Flutter 之 InheritWidget 源码浅析

675 阅读3分钟

InheritWidget 实现局部刷新必须注意到两个点:

  • InheritWidget 下的子 widget 必须是缓存过的
  • InheritWidget 下的子 widget 需添加进 _dependencies 集合中
  • InheritWidget 需要 rebuild

1、为什么 InheritWidget 下的子 widget 必须是缓存过的?

我们看下未做 widget 缓存的例子:

class InheritWidgetState extends State<InheritWidgetDemo> {
  int count = 0;
  @override
  Widget build(BuildContext context) {
    return Column(
        children: [
          ShareDataWidget(data: count, child: _TestWidget()),
          RaisedButton( child: Text("Increment"),onPressed: () => setState(() => ++count), // 触发 rebuild
          )
        ]);}}

class _TestWidget extends StatefulWidget {
    @override
    _TestWidgetState createState() => new _TestWidgetState();}

class  _TestWidgetState extends State<_TestWidget> {
    @override
    Widget build(BuildContext context) {  
      print("我被 build 了");
      return Text(ShareDataWidget.of(context,rebuild:true).data.toString());}}
  • 如果通过日志去查看的话,你会发现没有任何问题,单击 Increment , print 确实打出了被 build 的日志,你可能会觉得 _TestWidgetStaterebuild 设置为了true ,他就应该被 rebuild,没有任何问题
  • 但当把 rebuild 设置为 false 呢?按照我们对 InheritWidget 的理解来看,_TestWidgetState不应该被触发 build 操作,然而事实是,_TestWidgetState 仍然被触发,主要原因是 widget 未缓存,setState 会触发 widget 以及子 widget 的 build 操作,然后每次都会重新构建 _TestWidget

2、 如何缓存 widget

class InheritWidgetDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return HomePage(
        child:  WidgetA()
    );
  }
}

class HomePage extends StatefulWidget {
      HomePage({Key key, this.child, }) : super(key: key);
      // 缓存的 widget
      final Widget child;
      @override
      HomePageState createState() => HomePageState();
}

class HomePageState extends State<HomePage> {
 	 int counter = 0;
    void _incrementCounter() {
          setState(() {   ++counter;  });
     }
   @override 
   Widget build(BuildContext context) {
          print("home  ${widget.child.hashCode}"); // hashcode 一致
          return _MyInheritedWidget(
            data: this, child: widget.child,
   );}}
  • 为什么这么写可以?主要是 setState 触发的是 HomePage 的 build,并不会影响到 InheritWidgetDemo ,又因为 widget 是在 InheritWidgetDemo 创建的,所以 HomePage 拿到的一直是没有变化过的 widget,也可以通过打印 widget.hashCode 来验证一下是否一致

3、InheritWidget 下的子 widget 需添加进 _dependencies 集合中

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
           _MyInheritedWidget.of(context,rebuild: true).data.counter.toString(),
        ),
    );
  }
}
  • 如上是通过 InheritWidget 来实现数据展示,rebuild 为 true 即为 InheritWidget 发生数据改变时需要刷新
static _MyInheritedWidget of(BuildContext context, {bool rebuild = true}) {
        if (rebuild) {
          return (context.dependOnInheritedWidgetOfExactType<_MyInheritedWidget>());
        }
        return (context.getElementForInheritedWidgetOfExactType<_MyInheritedWidget>()).widget;
  }
  • rebuild 为 true 时,调用的是 dependOnInheritedWidgetOfExactType 方法
  @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
        assert(ancestor != null);
        _dependencies ??= HashSet<InheritedElement>();
       // 将当前 element 添加进 _dependencies 集合中
        _dependencies!.add(ancestor);
        ancestor.updateDependencies(this, aspect);
        return ancestor.widget;
  }

  @override
  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
        assert(_debugCheckStateIsActiveForAncestorLookup());
        final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
        if (ancestor != null) {
          assert(ancestor is InheritedElement);
          return dependOnInheritedElement(ancestor, aspect: aspect) as T;
        }
        _hadUnsatisfiedDependencies = true;
        return null;
  }
  • context 的实现类是 element ,最终会将当前 widget 的 element 缓存到 _dependencies 集合中

4、如何触发局部刷新

class HomePageState extends State<HomePage> {
 	 int counter = 0;
    void _incrementCounter() {
          setState(() {   ++counter;  });
     }
   @override 
   Widget build(BuildContext context) {
          return _MyInheritedWidget(
            data: this, child: widget.child,
   );}}
  • 在调用 _incrementCounter 方法触发 setState 时,会触发 HomePageState 的 rebuild 操作,从而影响到子 widget 的 _MyInheritedWidget rebuild,也就是 InheritWidget
abstract class ProxyElement extends ComponentElement {
   ....
     @override
  void update(ProxyWidget newWidget) {
    final ProxyWidget oldWidget = widget;
    ...
    updated(oldWidget);
    _dirty = true;
    rebuild();
  }
  • InheritWidget 的 element 是 InheritElement ,继承的是 ProxyElement,最终会走到 update 方法中来更新当前的 widget,可以直接看 updated 方法
-> 父类 ProxyElement  
@protected
  void updated(covariant ProxyWidget oldWidget) {
     notifyClients(oldWidget);
  }

-> 子类 InheritElement
  @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
 }  
  • 子类实现了父类的 updated 方法,子类会对 InheritWidget 的 updateShouldNotify 进行判断,如果返回 true ,则子 wdiget 会调用 didChangeDependencies 方法
  • 父类 updated 会触发 notifyClients 方法,notifyClients 是交由子类 InheritElement 来实现的
@override
void notifyClients(InheritedWidget oldWidget) {
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (final Element dependent in _dependents.keys) {
      ....
      // check that it really depends on us
      assert(dependent._dependencies!.contains(this));
      notifyDependent(oldWidget, dependent);
    }
  }
}
  • notifyClients 会遍历 _dependencies 集合的 element 元素,并判断 element 是否包含当前 rebuild 的 InheritWidget , 然后调用 notifyDependent 方法
  @protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }
  • notifyDependent 会调用 element 的 didChangeDependencies 方法
  @mustCallSuper
  void didChangeDependencies() {
    assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
    assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
    markNeedsBuild();// build
  }
  • 进入到 didChangeDependencies 方法中查看发现,element 会触发 markNeedsBuild 进行重新构建,也就是局部 widget 刷新

5、如何使局部刷新失效

1、InheritWidget 的 updateShouldNotify 实现类返回 false

2、不将 widget 添加到 _dependencies 集合中,可以采用 getElementForInheritedWidgetOfExactType