Flutter:一文读懂,解刨 Inherited 局部刷新原理

抛砖引玉,Element的刷新机制

我们知道flutter的整个视图层是一个树状结构,以父子节点的形式进行布局绘制。刚接触flutter时,我们使用setState来实现页面页面刷新。这种刷新方式我们称为全量刷新,刷新父节点,那么该父节点下的所有子节点都会执行build方法进行刷新。

setState 如何实现刷新?

setState 通过 Element.markNeedsBuild 实现刷新

//State 中的代码
@protected
void setState(VoidCallback fn) {
  ...省略
  _element!.markNeedsBuild();
}

//Element 中的代码
void markNeedsBuild() {
  ...省略
  _dirty = true;
  owner!.scheduleBuildFor(this);
}

父节点刷新如何触发子节点刷新?

首先,我们需要了解Element执行刷新时都做了哪些操作:

Element触发刷新会执行三个重要的方法 reBuild -> performRebuild -> build

结合源码,可以看到常用的像 StatefulElement,StatelessElement,ProxyElement等都继承自ComponentElement。我们来扒一扒这三个方法在ComponentElement做了怎样的实现。

//来自 Element
@pragma('vm:prefer-inline')
void rebuild() {
  ...省略
  performRebuild();
}

//来自 ComponentElement
@override
@pragma('vm:notify-debugger-on-exception')
void performRebuild() {
  ...
  Widget? built;
  built = build();
  ...
  updateChild(_child, built, slot);
}

//来自 ComponentElement
@protected
Widget build();

ComponenetElementperformRebuild方法进行了重写,同时执行了buildupdateChild。当配置有更新时(child.widget != newWidget),updateChild 会调用 child.update(newWidget)方法。

//来自 ComponentElement
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    ...省略
    if (hasSameSuperclass && child.widget == newWidget) {
    ...省略
    } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
      child.update(newWidget); 
    }
  return newChild;
}

注意: 执行child.update的前提条件是 child.widget != newWidget

Inherited 局部刷新机制

使用过InheritedWidget的同学都会发现:对InheritedWidget节点进行setState操作,它的子组件中只有依赖于状态的子组件重走了build方法,其余无依赖关系的子组件没有重新build

1. 干掉全量刷新

从源码分析,InheritedElement继承自ProxyElement,ProxyElement继承自ComponentElement

image.png
  • InheritedWidget包裹在StatefulWidget内,执行setState。触发 stateful 组件内部方法: ComponentElement -> performRebuild -> updateChild -> child.update
  • InheritedElement 作为子节点,被触发 update 方法。
// Proxy中对update方法进行了重写
@override
void update(ProxyWidget newWidget) {
  ...
  updated(oldWidget);
  _dirty = true;
  rebuild();
}

update方法中,执行了rebuild方法。从上文中我们得知,rebuild方法最终会执行updateChild,用以刷新子节点。InheritedElementProxyElement并没有对performRebuild方法进行策略重写修改。Interited屏蔽了全量刷新的高消耗策略,它是如何做到的呢?

对比 ProxyElementStatefulElement,我们找到了不同点,这里的 build 方法没有调用对应 Widget 对象的 build 方法,而是直接返回了 widget.child

// ProxyElement的 build 方法
@override
Widget build() => widget.child;

// StatefulElement 的 build 方法
@override
Widget build() => state.build(this);

结合上文分析的 child.update 触发条件,由于 build 方法没有重新构建,child.widget != newWidget不成立。所以子组件树不会重新build,不会被触发child.update方法

2. 关联刷新

Inherited 如何通知有依赖关系的组件进行刷新?我们仔细看看ProxyElement的内部实现:

abstract class ProxyElement extends ComponentElement {

  @override
  Widget build() => widget.child;

  @override
  void update(ProxyWidget newWidget) {
    ...省略
    updated(oldWidget);
    _dirty = true;
    rebuild();
  }

  @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }

  @protected
  void notifyClients(covariant ProxyWidget oldWidget);
}

可以看到,在update方法中多了一个方法流程: update -> updated -> notifyClient

InteritedElementnotifyClienet被实现,更新所有有依赖关系的子组件树

// 来自 InteritedElement
@override
void notifyClients(InheritedWidget oldWidget) {
  for (final Element dependent in _dependents.keys) {
    ...省略
    notifyDependent(oldWidget, dependent);
  }
}

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

//来自 Element
@mustCallSuper
void didChangeDependencies() {
  markNeedsBuild();
}

_dependentsInteritedElement 内部维护的一个 HashMap,存放所有与自己有依赖关系的 Element
通过 context.dependOnInheritedWidgetOfExactType 绑定依赖关系。

补充

  • updateShouldNotify方法用于 InheritedWidget 的子类实现,只有返回true时,才会触发子组件的 didChangeDependencies 方法。(前提是存在依赖关系),didChangeDependencies 会调用 markNeedsBuild

你可能感兴趣的:(Flutter:一文读懂,解刨 Inherited 局部刷新原理)