美文网首页
InheritedWidget 详细理解

InheritedWidget 详细理解

作者: 晨曦中的花豹 | 来源:发表于2023-02-01 10:05 被阅读0次

在dart中说到状态管理,想必大家都知道InheritedWidget,这里说一下我对InheritedWidget的理解
InheritedWidget本质有两大功能,

  • InheritedWidget数据向下传递(下层节点可以获取到InheritedWidget中的数据)
  • InheritedWidget的状态绑定(就是InheritedWidget被修改,会导致引用的地方数据刷新)

1.InheritedWidget数据向下传递

InheritedWidget 本质是一个Widget,Widget本身要创建一个Element,而Element中包含一个属性

PersistentHashMap<Type, InheritedElement>? _inheritedWidgets;

这个属性是一个Map其中存储着本身及上层所有的InheritedElement,每次创建新的Element,都会从上一层Element中获取到_inheritedWidgets,并赋值给自己的_inheritedWidgets

void _updateInheritance() {
    assert(_lifecycleState == _ElementLifecycle.active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }

上面说了InheritedWidget是个Widget,所以会想到它也应该会创建一个Element,没错这里创建的就是InheritedElement

abstract class InheritedWidget extends ProxyWidget {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const InheritedWidget({ super.key, required super.child });

  @override
  InheritedElement createElement() => InheritedElement(this);
...

进去后发现InheritedElement重写了_updateInheritance,在之前的基础上将自己加入到_inheritedWidgets中

 @override
  void _updateInheritance() {
    assert(_lifecycleState == _ElementLifecycle.active);
    final PersistentHashMap<Type, InheritedElement> incomingWidgets =
        _parent?._inheritedWidgets ?? const PersistentHashMap<Type, InheritedElement>.empty();
    _inheritedWidgets = incomingWidgets.put(widget.runtimeType, this);
  }

到这里我们就可以清晰的看到如果是普通的Element只需要获取到上一级Element的_inheritedWidgets,而InheritedElement会将自己也加入进去,如果此时有了下一级Element,那么他的_inheritedWidgets就包含了上一级的InheritedElement,这样设计的目的应该也是为了方便查询(不用递归了)
之后dependOnInheritedWidgetOfExactType这个方法会通过_inheritedWidgets找到对应的InheritedElement,以及InheritedWidget

@override
  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
    if (ancestor != null) {
      return dependOnInheritedElement(ancestor, aspect: aspect) as T;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

@override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies!.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget as InheritedWidget;
  }

到这里关于InheritedWidget获取逻辑应该是很清楚了.

2.InheritedWidget的状态绑定

举个例子Theme.of(context)这个在我们的项目中经常使用,我们通过这几代码来获取ThemeData,当ThemeData发生改变后,Theme.of(context)的地方都会刷新
Theme.of(context)这个里边其实调用的就是dependOnInheritedWidgetOfExactType,然后调用dependOnInheritedElement,而在dependOnInheritedElement中我们会发现,这样一句代码

ancestor.updateDependencies(this, aspect);

这里其实就是将context(其实就是element)添加到了InheritedElement的_dependents中

final Map<Element, Object?> _dependents = HashMap<Element, Object?>();

当InheritedWidget修改了之后会调用InheritedElement 的 notifyClients

@override
  void notifyClients(InheritedWidget oldWidget) {
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (final Element dependent in _dependents.keys) {
      assert(() {
        // check that it really is our descendant
        Element? ancestor = dependent._parent;
        while (ancestor != this && ancestor != null) {
          ancestor = ancestor._parent;
        }
        return ancestor == this;
      }());
      // check that it really depends on us
      assert(dependent._dependencies!.contains(this));
      notifyDependent(oldWidget, dependent);
    }
  }

@protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }

在didChangeDependencies中进行了对应element的重新build,setState本质也是调用了markNeedsBuild

@mustCallSuper
  void didChangeDependencies() {
    assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
    assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
    markNeedsBuild();
  }

而State中也有didChangeDependencies方法(经常会问到的生命周期函数),会在Element 的didChangeDependencies调用后,重新build的时候调用的

@override
  void performRebuild() {
    if (_didChangeDependencies) {
      state.didChangeDependencies();
      _didChangeDependencies = false;
    }
    super.performRebuild();
  }

_didChangeDependencies这个参数也是在StateFulElement中重写的didChangeDependencies中设置成了true

@override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _didChangeDependencies = true;
  }

到这里InheritedWidget的两大功能就说完了, InheritedWidget的向下传递以及InheritedWidget的状态绑定,统称为状态管理

补充:

之前有个问题我困惑了很久,就是InheritedWidget及其以下的widget,本身就会因为顶层的setState而刷新,为什么需要有_dependents的存在,去标记_didChangeDependencies呢?或许今天遇到的一个问题可以解释这样做的目的.
我的StatefulWidget使用const修饰的话,顶层的setState并不会导致const修饰的widget 重新 build,所以如果没有_dependents,对于const 修饰的widget我们就没办法去刷新,这个应该是_dependents存在的意义.
那为什么const修饰的widget不会重新执行build方法呢?
这里通过看源码发现build方法执行通道有两个,我觉得更好理解的是一个叫做刷新,一个叫做更新,刷新源头来自于本身,而更新源头来自于父组件

  • 1.通过设置element _dirty来标记(我就叫做刷新),下一帧绘制的时候强制刷新 WeChat7f1baf93ddea4be76bb4f8ebc35430bd.png
  • 2.是通过element树遍历来执行刷新(我就叫做更新)
    (遍历的这些element 的 _dirty 并不一定都是true,但是会重新执行build方法,这一点理解很重要,很多同学理解的build执行的前提是element被设置成 _dirty 是不对的)


    WeChat47333e1b40d75a7f15a4271972b8327c.png

方式2,因为在Element的updateChild方法中有这样一句代码

      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot) {
          updateSlotForChild(child, newSlot);
        }
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        ...
        child.update(newWidget);
        ...
        newChild = child;
      }

因为const修饰的widget是同一个对象,导致了 child.widget == newWidget成立,所以update(newWidget)不会执行,导致通过方法2,没有办法来实现build重新执行
而对于InheritedElement,本质就是一个Element,它也会在方式2中被遍历到,并且执行自己的update方法,但是在执行update方法时会有一个updated方法被执行,这个方法中会将_dependents中的所有element._dirty设置成true(其实markNeedsBuild,就是干这个事情的),这样就可以强制去刷新了.

相关文章

网友评论

      本文标题:InheritedWidget 详细理解

      本文链接:https://www.haomeiwen.com/subject/wpqbhdtx.html