讲在前面
对于刚上手开发Flutter的同学,想要实现一个Widget的刷新,除了使用StatefulWidget+setState方法,似乎没有什么更好的方式;
更深入一点之后发现,可以使用一些状态管理库来实现Widget的刷新,似乎更加方便而且规范了;
比如官方提供的Provider状态管理库,我们可以使用其提供的ChangeNotifierProvider来实现刷新,在我们的状态类(继承ChangeNotifier)中调用notifyListeners之后,我们在ChangeNotifierProviderchild内有声明context.watch()或使用Consumer/Selector等包裹的地方就会进行刷新;
比较神奇的地方在于,在一个复杂的Widget树中,这些库可以帮助我实现某些Widget的局部刷新,避免一些高频次的整体重建(尽管framework源码中对于Element树的构建有足够多的逻辑优化,我们还是需要尽量避免无意义的Widget刷新)
为了搞清楚这个机制的实现原理,这里我们自制一个简易版Provider作为切入口来分析:
先来个demo图示:

然后看看代码内容
示例代码
视图 & 状态
            
            
              dart
              
              
            
          
          class SamplePage extends StatelessWidget {
  const SamplePage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("SamplePage"),
      ),
      body: MyChangeNotifierProvider<SampleModel>(_buildBody(), SampleModel()),
    );
  }
  _buildBody() {
    return SizedBox.expand(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Builder(builder: (context) {
            return Text(
                "CountA is ${(MyInheritedProvider.of(context, listen: true).model as SampleModel).count.toString()}");
          }),
          Builder(builder: (context) {
            return Text(
                "CountB is ${(MyInheritedProvider.of(context, listen: false).model as SampleModel).count.toString()}");
          }),
          Builder(builder: (context) {
            return GestureDetector(
              onTap: () {
                (MyInheritedProvider.of(context, listen: false).model
                        as SampleModel)
                    .countIncrease();
              },
              child: const Text("launch"),
            );
          })
        ],
      ),
    );
  }
}
        
            
            
              dart
              
              
            
          
          class SampleModel extends MyChangeNotifier {
  int count = 0;
  countIncrease() {
    count++;
    notifyListener();
  }
}
        自制Provider相关
            
            
              dart
              
              
            
          
          class MyChangeNotifier {
  Function? notifyFunc;
  registerListener(Function func){
    notifyFunc = func;
  }
  notifyListener() {
    notifyFunc?.call();
  }
}
        
            
            
              dart
              
              
            
          
          class MyInheritedProvider<T extends MyChangeNotifier> extends InheritedWidget {
  final T model;
  const MyInheritedProvider(
    this.model, {
    Key? key,
    required Widget child,
  }) : super(key: key, child: child);
  static MyInheritedProvider of(BuildContext context, {bool listen = false}) {
    if (listen) {
      final MyInheritedProvider? result =
          context.dependOnInheritedWidgetOfExactType<MyInheritedProvider>();
      assert(result != null, 'No MyInheritedProvider found in context');
      return result!;
    } else {
      final MyInheritedProvider? result = context
          .getElementForInheritedWidgetOfExactType<MyInheritedProvider>()!
          .widget as MyInheritedProvider?;
      assert(result != null, 'No MyInheritedProvider found in context');
      return result!;
    }
  }
  @override
  bool updateShouldNotify(MyInheritedProvider old) {
    return true;
  }
}
        
            
            
              dart
              
              
            
          
          class MyChangeNotifierProvider<T extends MyChangeNotifier>
    extends StatefulWidget {
  final Widget child;
  final T model;
  const MyChangeNotifierProvider(this.child, this.model, {Key? key})
      : super(key: key);
  @override
  State<MyChangeNotifierProvider> createState() =>
      _MyChangeNotifierProviderState();
}
class _MyChangeNotifierProviderState extends State<MyChangeNotifierProvider> {
  doSetState() {
    setState(() {});
  }
  @override
  Widget build(BuildContext context) {
    return MyInheritedProvider(
      widget.model,
      child: widget.child,
    );
  }
  @override
  void didUpdateWidget(
      covariant MyChangeNotifierProvider<MyChangeNotifier> oldWidget) {
    super.didUpdateWidget(oldWidget);
    widget.model.registerListener(doSetState);
  }
}
        上面这一大坨代码,就是我们Demo的全部代码,分为两部分
- 我们自己的业务代码(第一部分)
 - 我们自制的ChangeNotifierProvider(第二部分)
 

这里我们为了更好的与官方的Provider库对应上,所以自制的Provider框架类前面都加了个my,方便理解;
这里我们说明下具体类的功能
| 类名 | 功能 | 
|---|---|
| sample_page | 页面类,其中展示了3个Text,前两个引用了SampleModel中的count值,但是A进行了监听,B没有 | 
| sample_model | 状态类,继承了MyChangeNotifier,其中只有一个count变量和改变count的简单方法 | 
| my_change_notifier | 仅仅存储一个Function,合适的时机调用该Function | 
| my_inherited_provider | 核心类,继承于InheritedWidget,其中存放一个数据模型,泛型限制继承于MyChangeNotifier。提供了一个获取当前类的方法,入参区分是否进行监听(关联依赖) | 
| my_change_notifier_provider | 核心类,继承于StatefulWidget,接收一个Widget和一个数据模型,最终在State的build方法中均传入MyInheritedProvider | 
简单介绍完之后,我们来具体分析一下:
SamplePage
首先是页面(SamplePage),比较简单,写法跟ChangeNotifierProvider一样,在页面之上包裹一个我们的MyChangeNotifierProvider,里面是一个列表,里面有三个Text,前两个是展示count数值,后一个是点击后去增长count值的;
注意这里我们的几个Widget都是用Builder包裹了一层,这里提前说明一下是为了使用其BuildContext去获取组件树上的MyInheritedProvider。
MyChangeNotifierProvider
然后我们来看一看MyChangeNotifierProvider类:

我们直接看其State类,很简单:
在didUpdateWidget钩子函数中将setState方法注册进入我们的MyChangeNotifier中;
didUpdateWidget方法在以下情况下会被调用:
- 当与该
 State对象关联的Widget重新构建并创建一个新的Widget实例时。- 当父
 Widget改变并重新构建该StatefulWidget时,Flutter 框架会调用didUpdateWidget方法。
build方法将外部传入的model和child都传入MyInheritedProvider中;
总的来说这个类的主要工作就是注册一下刷新方法,供状态类在某个时机下调用;并且在build时对于传入的Widget包裹了一层MyInheritedProvider返回。看起来这就是一个简单的中介类。
MyInheritedProvider
进入MyInheritedProvider,这是最核心的部分,此类继承于InheritedWidge;
InheritedWidget最重要的功能之一在于,可以通过BuildContext的dependOnInheritedWidgetOfExactType或getElementForInheritedWidgetOfExactType方法获取祖先组件树中的InheritedWidget类型的Widget,并且前者的方法中有一个依赖注册的功能,这点我们会分析到;
另外一点也很重要,InheritedWidget对应的Element是InheritedElement,其父类是ProxyElement,它是继承与ComponentElement的,不过它的build方法并不像StatelessElement一样是调用自己Widget的build方法,而是直接返回了Widget的child变量


好了,分析完这两个核心类,我们来看看代码运行后的表现以及为何如此。
回到我们的SamplePage,我们点击了"launch"按钮,此时调用了SampleModel中的countIncrease方法:将count++,并且调用notifyListener方法;
这个方法对应的就是_MyChangeNotifierProviderState中的setState方法,这时候build方法执行,重新返回了一个MyInheritedWidget;
可能刚接触Flutter的同学就出现疑惑了,理论上来说StatefulWidget调用了setState方法之后,其子类会进行刷新,而我们传入的三个Text都是StatefulWidget的子类(StatefulWidget - MyInheritedWidget - 3个Text),为什么会只刷新了其中的一个Text呢?
源码分析
我们开始追踪源码;
我们要知道一个前提:刷新Widget会先进入Element的rebuild方法。然后是performRebuild方法,这个方法Element没做什么,交由具体子类去实现。StatefulWidget的Element的是ComponentElement,所以我们来看看它的具体实现:

这里的build方法,即我们的MyInheritedProvider:

注意这里的build方法,是ComponentElement独有方法,这里返回的MyInheritedProvider即updateChild方法传入的built参数,这里解释下这3个入参:
| 变量 | 类型 | 含义 | 
|---|---|---|
| _child | Element | 当前Element持有的子Elmenet,第一次执行时或上一次没有child时为null | 
| built | Widget | 即调用自身build返回的Widget对象,build方法具体实现交由子类(比如我们常写的StatelessWidget中的build方法) | 
| slot | Object | slot 是一个用于标识元素在其父元素中的位置或角色的抽象概念。它通常用于复杂的布局逻辑,其中子元素之间的关系并不仅仅是一个简单的线性列表,这个点本文不做具体解释,此部分不影响本文分析内容 | 
接着看updateChild方法,我们先总结一下这个方法的工作:就是传入build返回的Widget和之前加载Element树时已生成的子Element做各种比较,判断要不要重新通过Widget生成一个新的Element,还是说仍然使用之前的Element子类,只是做一下更新Widget动作;
这里我们把不重要的代码先删除掉,图示分析一下这个方法中都做了什么:

回到我们的代码中,我们点击了"launch"按钮,执行了setState,然后进入performRebuild,又进入updateChild方法,这里child是第一次运行时就生成的InheritedElement,newWidget是传入的MyInheritedProvider;
- 条件1,判断不进入,因为
build方法返回的是一个新的MyInheritedProvider,跟之前Element持有的并不是同一个对象 - 条件2,判断进入,因为运行时类型是一样的(并且我们没有给
Widget传入key参数) 
那么这里就执行了child.update(newWidget)
update在Element类中只做了一个重新赋值_widget的操作:

我们还是要看具体子类有没有重写该方法,InheritedElement->ProxyElement->ComponentElement->Element
三个子类中只有ProxyElement进行了重写:

这里1稍微放一放,我们看看2的逻辑;
这里我们要记住我们当前执行的已经是在InheritedElement对象中的方法了,因为它跟StatefulElement一样也是ComponentElement的子类,最终也会走到上面的performRebuild方法,然后调用自己的build()方法,返回一个built传入updateChild方法;
好了,这里就是我们要重点分析的地方了,为什么没有刷新我们的业务Widget (这里就暂且称我们SamplePage中传入MyChangeNotifierProvider的child为业务Widget,即下图_buildBody中Widget)

一、我们setState刷新的是State类,而State#build方法中返回的MyInheritedProvider中的child不是重新创建的,而是一开始外部传入StatefulWidget中存储的;

二、记得上面StatefulElement这一层执行到了child.update(newWidget),进入了InheritedElement这一层,它的update方法中仅替换了Element持有的Widget对象(Element没有重新创建),然后进入了ComponentElement#performRebuild方法,这里执行了自己的build方法去获取一个Widget,这个Widget是什么呢?回顾一下 
它就是我们的业务Widget,一直作为child变量存储在ProxyWidget中,这里的ProxyElement#build方法只是将其拿了出来,并没有重新创建一个Widget;
三、那么看到我们的updateChild方法中(上翻一下updateChild方法图示),自然就进入了条件分支1中,因为等式两边都是我们的业务Widget(同一个对象)
所以,组件树从上向下更新的过程中到了这里就中断了,不会向下再进行了;
现在我们要来研究一下最后的问题:为什么组件Widget中的其中一个Text可以被刷新?
我们来看下两个Text分别怎么展示自己的text内容的: 
这个参数区分是使用了什么方法来获取组件树中的MyInheritedProvider
CountA使用的方法:BuildContext# dependOnInheritedWidgetOfExactType
CountB使用的方法:BuildContext# getElementForInheritedWidgetOfExactType
这里直接说明一下区别,前者方法比后者多一个功能: 
 
 
 
先在组件树祖先中找到指定类型的InheritedElement,然后将当前的Element依赖到对应的InheritedElement中,使用一个Map容器(_dependents)来存储;
那么这些容器里的Element又是在哪里被拿出来使用的呢?是怎么使用的呢?
还记得之前讲的setState之后,执行到了MyInheritedProvider对应Element的update方法吗(拿出来再看一眼)

我们进去看一看都有什么动作 
进入了ProxyElement的updated方法,不过它的子类InheritedElement重写了这个方法,看一眼 
还记得这个方法吗,我们的MyInheritedProvider继承于InheritedWidget,必须要重写这个方法,来决定是否应该通知依赖,我们为了简单直接return了true(可以根据具体业务决定是否通知),所以逻辑执行了super.updated。接着向里看 
 
 
终于,看到了熟悉的方法markNeedsBuild,把当前Element标记为需要更新,后续则通过BuildOwner展开了组件的刷新逻辑(这部分等同于StatefulWidget的State中调用了setState,不做展开了)
说在最后
总结一下:
到此为止,我们终于搞定了一个丐版的自制可局部刷新的状态管理框架~🎉🎉,至于官方ChangeNotifierProvider的实现逻辑,其实实现逻辑不尽相同,我们后续再专门做一篇分析;
这个整体的实现核心逻辑就是Flutter框架中提供的InheritedWidget组件,这个组件的重要性不亚于我们最常使用的StatelessWidget、StatefulWidget,了解了其核心逻辑,我们也可以使用它来写出一些优雅的框架等;
最后贴一下上述的demo,里面添加了部分注释,大家可以clone下来debug一下增加理解。
以上如有错误,欢迎指出!