InheritedWidget 源码分析和应用

目前,开发的flutter项目中,状态管理库使用是ProviderProvider 基于 InheritedWidget 组件封装,想要减小平常开发采坑,就不得不去了解 InheritedWidget 组件的工做原理。因为要从源码角度分析 InheritedWidget 组件的工做原理,在阅读本文前,最好对 flutter 的知识有必定了解,这样才能更好的了解,本文所要表达的意思。android

  • 熟悉 flutter 基本使用。
  • 了解 provider 的框架。
  • 了解 WidgetElement 之间关系。
  • 了解 Elementflutter 渲染时方法的调用。

1、 InheritedWidget

本文中用到的生产环境安全

生产环境

1.1 InheritedWidget 简述

/// 有效地沿树向下传播信息的 Widget 的基类,子 Widget 要想获取最近特定类型的 InheritedWidget实例,请使用 
/// [BuildContext.dependOnInheritedWidgetOfExactType]。子 Widget 以这种方式引用时,当 InheritedWidget 改变状态
/// 时,会从新构建依赖的子 Widget。
abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({ Key key, Widget child })
      : super(key: key, child: child);

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

  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);

复制代码

InheritedWidget 代码很简单,主要有两个方法:markdown

  • createElement 生成 InheritedElement 对象。
  • updateShouldNotify 用于控制当前 InheritedWidget 发生变化, 所依赖的 Widget 是否须要重建。
1.1.1 BuildContext

InheritedWidget 描述可得知,若是子 Widget 须要获取 InheritedWidget 对象,能够经过 BuildContext.dependOnInheritedWidgetOfExactType 获取。看下 BuildContext 类的 dependOnInheritedWidgetOfExactTypeapp

abstract class BuildContext {
  /// 获取给定类型“T”的最近 widget,它必须是具体 [InheritedWidget] 子类的类型,并将此构建上下文注册到该 widget,以便当该 widget 更改时(或引入该类型的新 widget, 或 widget 消失),此构建上下文将被重建,以便它能够从该 widget 获取新值。
   T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object aspect });
  
  /// 获取与给定类型“T”的最近 widget 对应的 element,该 element 必须是具体 [InheritedWidget] 子类的类型。 若是找不到这样的 element,则返回 null。 
  /// 调用这个方法是 O(1) 一个小的常数因子。 此方法不会像 [dependOnInheritedWidgetOfExactType] 那样与目标创建关
  /// 系。 不该从 [State.dispose] 调用此方法,由于此时 element 树再也不稳定。 要从该方法引用祖先,请经过在 
  /// [State.didChangeDependencies] 中调用 [dependOnInheritedWidgetOfExactType] 来保存对祖先的引用。 使用 
  /// [State.deactivate] 中的此方法是安全的,每当 widget 从树中移除时都会调用该方法。
  InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>();
}
复制代码

1.2 InheritedWidget 应用

接下来,咱们写一个基于 InheritedWidget 组件实现的 计数器框架

1.2.1 CountScope
class CountScope extends InheritedWidget {
  const CountScope({this.count, Widget child}) : super(child: child);

  final int count;

  static CountScope of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CountScope>();
  }

  @override
  bool updateShouldNotify(CountScope oldWidget) {
    return oldWidget.count != count;
  }
}
复制代码

CountScope 继承 InheritedWidgetless

  1. of ,子 Widget 获取 CountScope 对象。[见1.1小节]
  2. updateShouldNotifyoldWidget.count != count 刷新依赖的 widget[见1.1小节]
1.2.2 CountWidget
class CountWidget extends StatefulWidget {
  const CountWidget({Key key}) : super(key: key);

  @override
  _CountWidgetState createState() => _CountWidgetState();
}

class _CountWidgetState extends State<CountWidget> {
  @override
  Widget build(BuildContext context) {
    print('_CountWidgetState build');
    final int count = CountScope.watch(context).count;
    return Container(child: Text('$count', style: Theme.of(context).textTheme.headline4));
  }
}
复制代码

CountWidget 调用 CountScope.of(context).count 显示计数结果。ide

1.2.3 MyHomePage
class MyHomePage extends StatefulWidget {
  const MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    print('_MyHomePageState build');
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            CountScope(count: _counter, child: CountWidget()),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
复制代码

到这里计数器功能差很少就完成了,当咱们点击浮动 + 按钮时,计数器的数值会累加,最后能够看到效果就是这样。函数

1.2.4 updateShouldNotify

咱们在 1.1 小节 提过, updateShouldNotify 返回 true ,表示更新依赖的 Widgetfalse 不更新,如今让咱们验证下。修改 CountScope 代码,当 count < 3,才能更新依赖的 widget性能

class CountScope extends InheritedWidget {
  const CountScope({this.count, Widget child}) : super(child: child);

  final int count;

  static CountScope watch(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CountScope>();
  }

  @override
  bool updateShouldNotify(CountScope oldWidget) {
     //return oldWidget.count != count;
    print('count:$count');
    return count < 3;
  }
}
复制代码

结果确实如咱们所料那样,不停的点击 + 按钮,界面上的数字一直显示的是 2。认真观察 log 会发现,实际上 count 是会一直累加。优化

3.png

讲到这里,咱们就会有不少疑问?为何 CountWidget 能获取到 CountScopecount 的值?又为何CountScopecount 数值有变化,可是当 count > 3 时,CountWidget 界面却没有更新呢?

遇事不决,看源码

2、Widget 更新流程

咱们知道调用 setState 后,把当前 element 标记为 dirty, 当下一次 vsync 信号到来的时候,回调执行 handleBeginFramehandleDrawFrame ,而后通过一些列的调用,最后会调用 BuildOwner.buildScope ,遍历 _dirtyElements 集合,调用 Elementrebuild 刷新组件。

本文不会完整介绍 Widget 的更新机制,有兴趣的同窗,能够本身去了解下。(接下来源码片断,都只保留关键代码)。

2.1 类图

4.png

2.2 时序图

5.png

2.3 Element#rebuild

前面,咱们提到调用 setState 后 ,通过一系列的调用,最终调用 Elementrebuild

abstract class Element extends DiagnosticableTree implements BuildContext {
  /// 当调用 [BuildOwner.scheduleBuildFor] 会将该 Element 标记为 dirty,
  /// 当 Element 首次 build 时会被 [mount] 调用,当 widget 更改时由 [update] 调用。
  void rebuild() {
    if (!_active || !_dirty)
      return;
    performRebuild();
  }

  /// 在进行适当的检查后由 rebuild() 调用。
  @protected
  void performRebuild(); /// [见2.4小节]
}
复制代码

2.4 ComponentElement#performRebuild

abstract class ComponentElement extends Element {

  /// 调用 StatelessWidget 对象的 StatelessWidget.build 方法(对于无状态小部件)
  /// 或 State 对象的 State.build 方法(对于有状态小部件),而后更新 widget 树。
  /// 在 mount 期间自动调用以生成第一个构建,并在 element 须要更新时经过 rebuild 调用。
  @override
  void performRebuild() {
    Widget built;
    try {
      built = build();  
    } finally {
      /// ...
      _dirty = false;
    }
    try {
      _child = updateChild(_child, built, slot); /// [见2.5小节]
    } catch (e, stack) {
      // ...
      _child = updateChild(null, built, slot);
    }

  /// 子类应该覆盖这个函数来为它们的小部件实际调用适当的 `build` 函数
  ///(例如,[StatelessWidget.build] 或 [State.build])。
  @protected
  Widget build();
}
复制代码

2.5 Element#updateChild

abstract class Element extends DiagnosticableTree implements BuildContext {

  @protected
  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }
    Element newChild;
    if (child != null) {
      bool hasSameSuperclass = true;
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget); /// [见2.6小节]
        newChild = child;
      } else {
        deactivateChild(child);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else { 
      newChild = inflateWidget(newWidget, newSlot); /// [见2.5.1小节]
    }
    return newChild;
  }
}
复制代码
2.5.1 Element#inflateWidget

若是第一次调用 updateChild,默认 child = null,就会执行 inflateWidget,生成 Element newChild 对象,最后把 newChild 赋值给 _child [2.4小节],后续就使用 newChild 传入 updateChild

Element inflateWidget(Widget newWidget, dynamic newSlot) {
    final Key key = newWidget.key;
    if (key is GlobalKey) {
      final Element newChild = _retakeInactiveElement(key, newWidget);
      if (newChild != null) {
        // ...
        newChild._activateWithParent(this, newSlot);
        final Element updatedChild = updateChild(newChild, newWidget, newSlot);
        return updatedChild;
      }
    }
    final Element newChild = newWidget.createElement();
    newChild.mount(this, newSlot);// [见2.5.2小节]
    return newChild;
  }
复制代码

调用 widget.createElement,生成 Element 对象,而后调用 Elementmount,递归调用,完成全部子 widget 的刷新。

2.5.2 Element#mount
void mount(Element parent, dynamic newSlot) {
    //...
    final Key key = widget.key;
    if (key is GlobalKey) {
      key._register(this);
    }
    _updateInheritance();
  }
复制代码
void _updateInheritance() {
    _inheritedWidgets = _parent?._inheritedWidgets;
  }
复制代码

将父类中的 _inheritedWidgets 集合对象,传到子类。

2.6 Element#update

[2.5.1] [2.5.2] 两个小节,描述第一次加载 widget 过程。接下来介绍 child.update(newWidget)

@mustCallSuper
  void update(covariant Widget newWidget) { /// [见2.7小节]
    _widget = newWidget;
  }
复制代码

经过查看 [2.1] 类图,发现最下层的子类是 InheritedElement

2.7 InheritedElement#update

class InheritedElement extends ProxyElement {
	@override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget); /// [见2.8小节]
  }
}
复制代码

widget.updateShouldNotify(oldWidget) 这个方法是否是似曾相识,这个就是一开始,咱们说的 InheritedWidget.updateShouldNotify [见1.2.4小节],这里也验证以前说法,若是 true 表示会继续后续的,false 不执行后续的操做。咱们接着看 super.updated 后面的流程。

2.8 ProxyElement#update

经过查看 [2.1] 类图, InheritedElement 继承自 ProxyElement

abstract class ProxyElement extends ComponentElement {
  @override
  ProxyWidget get widget => super.widget as ProxyWidget;

  @override
  Widget build() => widget.child;
  
  /// ...
  @override
  void update(ProxyWidget newWidget) {
    final ProxyWidget oldWidget = widget;
		/// ...
    updated(oldWidget);
    _dirty = true;
    rebuild();
  }
}
复制代码
/// 当 widget 更改时, 在 build 期间调用。
  /// 默认状况下,调用 notifyClients。 子类能够覆盖此方法以免没必要要地调用 notifyClients(若是旧的和新的小部件是等效的)。
  @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }
复制代码
/// 通知其余对象与此 Elment 关联的 Widget 已更改。
  /// 在更改与此元素关联的 Widget 以后但在重建此元素以前,在update期间(经过updated )调用
  @protected
  void notifyClients(covariant ProxyWidget oldWidget); // [见2.9小节]
复制代码

2.9 InheritedElement#notifyClients

class InheritedElement extends ProxyElement {
  final Map<Element, Object> _dependents = HashMap<Element, Object>(); 
	
  /// [2.5.2小节] 初始化时调用
  @override
  void _updateInheritance() {
    final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
    if (incomingWidgets != null)
      _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
    else
      _inheritedWidgets = HashMap<Type, InheritedElement>();
    _inheritedWidgets[widget.runtimeType] = this;
  }

  /// 返回使用 [setDependencies]为 [dependent] 记录的依赖项值。
  /// 每一个依赖元素都映射到单个对象值,该值表示元素如何依赖此 [InheritedElement]。
  /// 默认状况下,此值为 null,而且默认状况下会无条件重建相关元素。
  /// 子类可使用 [updateDependencies] 管理这些值
  /// 以便他们能够选择性地重建 [notifyDependent] 中的依赖项。
  /// 此方法一般仅在 [updateDependencies] 的覆盖中调用。
  /// 也能够看看:
  /// [updateDependencies],每次使用 [dependOnInheritedWidgetOfExactType] 建立依赖项时都会调用它。
  /// [setDependencies],设置依赖元素的依赖值。
  /// [notifyDependent],能够覆盖它以使用依赖项的依赖项值来决定是否须要重建依赖项。
  /// [InheritedModel],这是一个使用该方法管理依赖值的类的例子。
  @protected
  Object getDependencies(Element dependent) {
    return _dependents[dependent];
  }

  /// 为dependent设置getDependencies值返回的值。
  /// 每一个依赖元素都映射到单个对象值,该值表示元素如何依赖此InheritedElement 。 
  /// [updateDependencies]方法在默认状况下将该值设置为null,以便无条件地重建依赖元素。
  /// 子类可使用[updateDependencies]管理这些值,以便它们能够有选择地在[NotifyDependency]中重建依赖项。
  /// 此方法一般仅在updateDependencies覆盖中调用。
  @protected
  void setDependencies(Element dependent, Object value) {
    _dependents[dependent] = value;
  }

  /// 添加新的 [dependent] 时由 [dependOnInheritedWidgetOfExactType] 调用。
  /// 每一个依赖元素均可以映射到单个对象值 [setDependencies]。 此方法可使用 [getDependencies] 查找现有依赖项。
  /// 默认状况下,此方法将 [dependent] 的继承依赖项设置为 null。 这仅用于记录对 [dependent] 的无条件依赖。
  /// 子类能够管理本身的依赖项值,以便它们能够在 [notifyDependent] 中重建依赖项。
  /// [getDependencies],返回依赖项的当前值元素。
  /// [setDependencies],设置依赖元素的值。
  /// [notifyDependent],能够覆盖它以使用依赖的依赖值来决定是否须要重建依赖。
  /// [InheritedModel],这是一个使用该方法的类的例子管理依赖值。
  @protected
  void updateDependencies(Element dependent, Object aspect) {
    setDependencies(dependent, null);
  }

  /// 由 [notifyClients] 为每一个依赖调用。
  /// 默认状况下调用 [dependent.didChangeDependencies()] 。
	/// 子类能够覆盖此方法以根据 [getDependencies] 的值有选择地调用 [didChangeDependencies] 。
	/// 也能够看看:
	/// updateDependencies ,每次使用 [dependOnInheritedWidgetOfExactType] 建立依赖项时都会调用它。
	/// getDependencies ,它返回依赖元素的当前值。
	/// setDependencies ,它设置依赖元素的值。
	/// InheritedModel ,这是使用此方法管理依赖项值的类的示例。
  @protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies(); /// [见2.9.1]
  }

  /// 经过调用Element.didChangeDependencies通知全部依赖的Elements,这个widget已经更改。
  /// 此方法只能在 build 阶段调用。 一般当 Inherited widget 被重建时,这个方法会自动调用,例如,做为在 Inherited widget上方
  /// 调用State.setState的结果
  @override
  void notifyClients(InheritedWidget oldWidget) { 
    for (final Element dependent in _dependents.keys) {
      /// ...
      notifyDependent(oldWidget, dependent);
    }
  }
}
复制代码
2.9.1 Element#didChangeDependencies
void didChangeDependencies() {
   markNeedsBuild();
}
复制代码

看到 markNeedsBuild 是否是很熟悉。没错,就是咱们常用 setState,调用同样的方法。

void setState(VoidCallback fn) {
    // ...
    final dynamic result = fn() as dynamic;
    _element.markNeedsBuild();
  }
复制代码

上面咱们发现 notifyClients,遍历循环 _dependents.keys,调用 Element.didChangeDependencies 更新依赖。咱们看下 dependents 是怎么来的呢?咱们看 updateDependencies 注释,是调用 context.dependOnInheritedWidgetOfExactType 添加 Element

2.10 Element#dependOnInheritedWidgetOfExactType

ElementBuildContext 具体实现类

@override
  T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
    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 }) {
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
复制代码
2.11 流程图

下图,只是简单画了 InheritedElement 更新依赖组件的大体流程图,方便你们吸取,具体的细节还须要本身去挖掘。

6.png

2.12 小结

dependOnInheritedWidgetOfExactType 经过 inheritedWidgets[T] 获取到对应的 InheritedElement,当 InheritedElement不为空,接着调用 InheritedElementupdateDependencies,把当前 Element 注入到要获取 InheritedElement_dependents.keys 集合,接着返回 InheritedElementwidget 对象。当InheritedWidget 更新时,先经过 updateShouldNotify 判断当前 InheritedElement 是否能进行 updated,当值为 true 时,经过循环遍历 _dependents.keys 集合,来更新全部依赖的 widget

3、自定义 Provider

前面写了一个简单的 计数器,同时也介绍了 InheritedWidget 更新依赖 widget 的原理分析。那咱们能不能基于 InheritedWidget 实现一个本身的 Provider 功能呢?接下来,咱们基于以前的 计数器 的代码,一步一步优化,实现一个咱们本身的 Provider

3.1 优化方案(一)

经过上面咱们能发现,在主页面直接调用 setState,是比较消耗性能,应该把 incrementCounter 操做剥离出去,单独一个 model,这样彷佛更符合实际的开发。

3.1.1 CountModel

新建 CountModel 用来进行数据处理及页面更新操做,有点相似 androidviewModel

class CountModel with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void incrementCounter() {
    _count++;
    notifyListeners();
  }
}
复制代码
3.1.2 CountProvider

初始化传入数据源 T, 继承 ChangeNotifier ,监听 T notifyListeners 时,刷新 CountProvider,顺便把 dependOnInheritedWidgetOfExactTypeCountScope 移到 CountProvider 类中。

typedef WidgetBuilder = Widget Function(BuildContext context);

class CountProvider<T extends ChangeNotifier> extends StatefulWidget {
  const CountProvider({
    Key key,
    this.value,
    this.builder,
  }) : super(key: key);

  final T value;

  final WidgetBuilder builder;

  static T of<T extends ChangeNotifier>(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CountScope<T>>().value;
  }

  @override
  _CountProviderState<T> createState() => _CountProviderState<T>();
}

class _CountProviderState<T extends ChangeNotifier> extends State<CountProvider<T>> {
  void _changeValue() {
    setState(() {});
  }

  @override
  void initState() {
    super.initState();
    widget.value?.addListener(_changeValue);
  }

  @override
  void dispose() {
    widget.value?.removeListener(_changeValue);
    super.dispose();
  }

  @override
  void didUpdateWidget(covariant CountProvider<T> oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.value != widget.value) {
      oldWidget.value?.removeListener(_changeValue);
    }
    widget.value?.addListener(_changeValue);
  }

  @override
  Widget build(BuildContext context) {
    print('_CountProviderState build');
    return CountScope<T>(
      value: widget.value,
      child: Builder(
        builder: (BuildContext context) {
          return widget.builder(context);
        },
      ),
    );
  }
}
复制代码
3.1.3 CountScope
class CountScope<T extends ChangeNotifier> extends InheritedWidget {
  const CountScope({@required this.value, Widget child}) : super(child: child);

  final T value;

  @override
  bool updateShouldNotify(CountScope<T> oldWidget) {
    return true;
  }
}
复制代码
3.1.4 CountWidget
class CountWidget extends StatefulWidget {
  const CountWidget({Key key}) : super(key: key);

  @override
  _CountWidgetState createState() => _CountWidgetState();
}

class _CountWidgetState extends State<CountWidget> {
  @override
  Widget build(BuildContext context) {
    print('_CountWidgetState build');
    return Container(
      child: Text(
        '${CountProvider.of<CountModel>(context).count}',
        style: Theme.of(context).textTheme.headline4,
      ),
    );
  }
}
复制代码
3.1.5 MyHomePage
class MyHomePage extends StatefulWidget {
  const MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    print('_MyHomePageState build');
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            CountProvider<CountModel>(
              value: CountModel(),
              builder: (BuildContext context) {
                return Column(children: [
                  const CountWidget(),
                  ClickButton(),
                ]);
              },
            ),
          ],
        ),
      ),
    );
  }
}
复制代码
3.1.6 ClickButton
class ClickButton extends StatelessWidget {
  const ClickButton({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('ClickButton build');
    return ElevatedButton(
      onPressed: () {
        print('--- 点击 ---');
        CountProvider.of<CountModel>(context).incrementCounter();
      },
      child: const Icon(Icons.add),
    );
  }
}
复制代码

当咱们点击 + 时,能正常更新数据,咱们看 log 会发现, MyHomePage 页面没有从新进行 build,若是咱们在MyHomePageColumn 组件里面,再添加一个不依赖 CountProvider 的组件,会发生什么?

3.1.7 Nothing
class Nothing extends StatefulWidget {
  const Nothing({Key key}) : super(key: key);

  @override
  _NothingState createState() => _NothingState();
}

class _NothingState extends State<Nothing> {
  @override
  Widget build(BuildContext context) {
    print('_NothingState build');
    return Container(
      child: const Text('我是一个不依赖的 widget'),
    );
  }
}
复制代码
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    print('_MyHomePageState build');
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            CountProvider<CountModel>(
              value: CountModel(),
              builder: (BuildContext context) {
                return Column(children: [
                  const CountWidget(),
                  Nothing(), /// 新增
                  ClickButton(),
                ]);
              },
            ),
          ],
        ),
      ),
    );
  }
}
复制代码

结果以下图:

能够发现,每次咱们点击 + ,调用 CountModel 进行 notifyListeners 更新时,都会致使 CountProvider 的子 Widget 所有刷新,不论是否依赖。

3.1.8 小结

如今咱们会发现有三个问题:

  1. 当前 Widget 调用 CountModel 方法时,能够不进行依赖绑定?
  2. 若是咱们点击 Flutter Hot Reload 按钮,会发现计数器的 1,会变成 0,这合理吗?
  3. 有没有只会更新依赖的 widget,不更新 InheritedElement 组件下,全部的子 widget ?

3.2 优化方案(二)

3.2.1 InheritedElement

由于咱们是在 CountProvider 中监听 CountScope 的数据源变化,调用 setState 刷新整个 CountProvider,致使子 widget 也都所有更新,因此,咱们应该把数据源更新监听移到 CountScope,让 CountScope 通知刷新依赖的 widget 比较合理。

class CountScope<T extends ChangeNotifier> extends InheritedWidget {
  const CountScope({@required this.value, Widget child}) : super(child: child);

  final T value;

  @override
  bool updateShouldNotify(CountScope<T> oldWidget) {
    /// 由于是 InheritedElement 进行刷新,因此,这里能够设置为 false
    return false;
  }

  @override
  CountScopeElement<T> createElement() {
    return CountScopeElement<T>(value, this);
  }
}

class CountScopeElement<T extends ChangeNotifier> extends InheritedElement {
  CountScopeElement(this.value, InheritedWidget widget) : super(widget) {
    value?.addListener(_handleUpdate);
  }

  final T value;

  void _handleUpdate() {
    markNeedsBuild();
  }

  @override
  void unmount() {
    value?.removeListener(_handleUpdate);
    super.unmount();
  }

  @override
  Widget build() {
    notifyClients(widget);
    return super.build();
  }
}
复制代码

新建 CountScopeElement 继承 InheritedElement,在 CountScopecreateElement 返回自定义 CountScopeElement 类。在 CountScopeElement 实现数据源变化监听,在销毁时调用移除监听。

  1. _handleUpdate 调用 markNeedsBuild ,这个方法咱们应该都很熟悉,也就是 setState 调用后,调用一样的方法,把 CountScopeElement 标记为 dirtybuild 调用 notifyClients(widget) ,可能会人有不明白,为何这里要调用 notifyClients

  2. 经过 [2.8小节] 咱们能够发现,当 CountScopeElement 进行 build 时,其实这里的 child 没有更新,仍是同一个对象。[见2.5小节],当 child.widget == newWidget 时,是不进行 child.update() 操做,也就没有后续的一串依赖更新操做。因此,这里要重写 build ,手动调用内部的 notifyClients,通知依赖更新操做。

3.2.2 getElementForInheritedWidgetOfExactType

经过 [1.1小节] 咱们知道,获取 InheritedWidgetdependOnInheritedWidgetOfExactType,并建立依赖关系。咱们经过 getElementForInheritedWidgetOfExactType 获取 InheritedElement,这个过程,不建立依赖关系,彷佛能解决须要依赖的问题,咱们修改 CountProviderof 以下:

static T of<T extends ChangeNotifier>(BuildContext context, {bool listen = true}) {
    final InheritedElement inheritedElement = context.getElementForInheritedWidgetOfExactType<CountScope<T>>();
    final CountScopeElement<T> countScopeElement = inheritedElement as CountScopeElement<T>;
    if (listen) {
      context.dependOnInheritedElement(inheritedElement);
    }
    return countScopeElement.value;
  }
复制代码

实现思路,获取到 CountScopeElement ,若是 listen = true 时,则进行依赖更新,最后返回 CountScopeElement.value

3.2.3 CountProvider
class CountProvider<T extends ChangeNotifier> extends StatefulWidget {
  const CountProvider({
    Key key,
    this.value,
    this.builder,
  }) : super(key: key);

  /// 数据源
  final T value;

  final WidgetBuilder builder;

  static T of<T extends ChangeNotifier>(BuildContext context, {bool listen = true}) {
    final InheritedElement inheritedElement = context.getElementForInheritedWidgetOfExactType<CountScope<T>>();
    final CountScopeElement<T> countScopeElement = inheritedElement as CountScopeElement<T>;
    if (listen) {
      context.dependOnInheritedElement(inheritedElement);
    }
    return countScopeElement.value;
  }

  @override
  _CountProviderState<T> createState() => _CountProviderState<T>();
}

class _CountProviderState<T extends ChangeNotifier> extends State<CountProvider<T>> {

  @override
  Widget build(BuildContext context) {
    print('_CountProviderState build');
    return CountScope<T>(
      value: widget.value,
      child: Builder(
        builder: (BuildContext context) {
          return widget.builder(context);
        },
      ),
    );
  }
}
复制代码

最终效果是可行的,也解决了 [3.1.8小节] 提出的三个问题。上面的代码看起来彷佛没什么问题,若是 _MyHomePageState 在进行 setState 操做,会发现什么事情?

class _MyHomePageState extends State<MyHomePage> {
  void _refresh() {
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    print('_MyHomePageState build');
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            CountProvider<CountModel>(
              value: CountModel(),
              builder: (BuildContext context) {
                return Column(children: [
                  const CountWidget(),
                  Nothing(),
                  ElevatedButton(
                    onPressed: () {
                      print('--- 点击 ---');
                      CountProvider.of<CountModel>(context, listen: false).incrementCounter();
                    },
                    child: const Icon(Icons.add),
                  ),
                ]);
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _refresh,
        tooltip: 'refresh',
        child: const Icon(Icons.refresh),
      ),
    );
  }
}
复制代码

每次点击 refresh,也会刷新 CountProvider 的依赖的子 widget ,这彷佛并无达到咱们的要求。那该怎么解决呢?

3.3 优化(三)

3.3.1 InheritedBuildContext

抽象类, CountScopeElement 剥离出来须要用到功能和 value,方便后期扩展。

abstract class InheritedBuildContext<T> {
  void needsBuild();

  T get value;
}
复制代码
3.3.2 Delegate

代理类 ,CountScopeElement 中方法的实现,value 的赋值。

abstract class Delegate<T> {
  CountScopeElement<T> element;

  T get value;
}
复制代码
3.3.3 CountDelegate

Delegate 的实现类,主要实现了数据的监听,通知 InheritedElement 刷新依赖。

class CountDelegate<T extends ChangeNotifier> extends Delegate<T> {
  CountDelegate({this.notifier});

  T notifier;

  @override
  T get value {
    notifier.addListener(() {
      element.needsBuild();
    });
    return notifier;
  }
}
复制代码
3.3.4 CountScope
class CountScope<T> extends InheritedWidget {
  const CountScope({@required this.delegate, Widget child}) : super(child: child);

  final Delegate<T> delegate;

  @override
  bool updateShouldNotify(CountScope<T> oldWidget) {
    return false;
  }

  @override
  CountScopeElement<T> createElement() {
    return CountScopeElement<T>(delegate, this);
  }
}

class CountScopeElement<T> extends InheritedElement with InheritedBuildContext<T> {
  CountScopeElement(this.delegate, InheritedWidget widget) : super(widget);

  final Delegate<T> delegate;
  bool _dirty = false;

  @override
  CountScope<T> get widget => super.widget as CountScope<T>;

  @override
  void performRebuild() {
    delegate.element = this;
    super.performRebuild();
  }

  @override
  Widget build() {
    if (_dirty) {
      _dirty = false;
      notifyClients(widget);
    }
    return super.build();
  }

  @override
  void needsBuild() {
    _dirty = true;
    markNeedsBuild();
  }

  @override
  T get value => delegate.value;
}
复制代码

主要修改,当须要从新 build 时,_dirty 标记为 true,当 build 完成时,再设置为 false,避免当CountScope 刷新,致使依赖的子 widget 也所有刷新。

3.3.5 CountProvider

集成 SingleChildStatelessWidget

class CountProvider<T extends ChangeNotifier> extends SingleChildStatelessWidget {
  CountProvider({Key key, this.value, this.builder})
      : _delegate = CountDelegate<T>(
          notifier: value,
        ),
        super(key: key);

  final T value;
  final WidgetBuilder builder;
  final Delegate<T> _delegate;

  static T of<T extends ChangeNotifier>(BuildContext context, {bool listen = true}) {
    final InheritedElement inheritedElement = context.getElementForInheritedWidgetOfExactType<CountScope<T>>();
    final CountScopeElement<T> countScopeElement = inheritedElement as CountScopeElement<T>;
    if (listen) {
      context.dependOnInheritedElement(inheritedElement);
    }
    return countScopeElement.value;
  }

  @override
  Widget buildWithChild(BuildContext context, Widget child) {
    print('CountProvider buildWithChild');
    return CountScope<T>(delegate: _delegate, child: Builder(builder: builder));
  }
}
复制代码

最终结果:当 CountScope 从新 rebuild 时,依赖的子 widget 不会跟着一块儿 rebuild

3.3.5 小结

小结:这里优化三个方案,也只是带你们发现问题,一步一步去解决问题。其实,跟你们的业务结合的话,这上面的代码,或许还会有不知足务求的地方。可是,无论什么框架,也都是一步一步优化过来的。只要有了解决问题的思路和想法,我相信,任何问题都不在是问题。

4、 总结

前面分析 InheritedWidget 的源码,调用流程等,在实际开发过程当中,咱们可能不会直接接触 InheritedWidget ,可是,仍是能发现一些系统的组件,使用 InheritedWidget 进行封装开发。例如:ThemeFocusButtonBarTheme 等,就不一一举例了,你们能够去看下源码。写这篇文章,一方面是为了巩固知识,方便往后查看、回忆知识点。另外一方便也是抛砖引玉,让更多人了解 InheritedWidget,若是你们认真看,其实仍是会发现一些 provider 的影子。

若是文章有什么不足的地方,欢迎批评指正,谢谢~

相关文章
相关标签/搜索