Flutter状态管理——深入理解InheritedWidget

什么是状态,状态管理

在响应式编程中,状态即数据,状态变化,页面即发生变化。状态变化分为两种:

  • 当前页面状态的改变
  • 跨widget的状态数据共享

而状态管理,更多情况下针对的是跨widget的状态管理。

常用的状态管理:

  • InheritedWidget
  • scoped_model
  • Provider
  • flutter_redux

概述

InheritedWidget是一个无界面的功能widget,主要作用是widget树中自顶向下的数据共享。

一个小示例,先知道怎么使用。

还是以计数器为例, 概要流程:

  • 自定义一个类MyInheritedWidget,继承InheritedWidget 。定义待共享的数据data;重写方法updateShouldNotify,该方法用于判断什么条件下通知更新;提供of方法用于获取MyInheritedwidget的实例。
  • 自定义DependWidget,内部使用MyInheritedWidget的字段data。
  • 构建widget树时,让MyInheritedDepend成为DependWidget的父类。
  • 当MyInheritedWidget的data发生变化时,会调用updateShouldNotify方法判断是否更新,如果更新,则最终将DependWidget的dirty置为true,在下一帧刷新。同时回调DependWidget的didChangeDependencies方法。
  • 特别强调:依赖者要成为MyInheritedWidget的子孙widget,否则无法共享数据。

自定义MyInheritedWidget 定义共享数据

class MyInheritedWidget extends InheritedWidget {
  final int data;
  MyInheritedWidget({
    @required this.data, //待共享的数据
    Widget child,
  }) : super(child: child);

  //获取当前MyInheritedWidget的实例
  static MyInheritedWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
  }

  @override
  bool updateShouldNotify(MyInheritedWidget oldWidget) {
    // 新旧数据不一致时,返回true,通知依赖本widget的子widget,此时子widget中的didChangeDependencies方法会被调用
    return oldWidget.data != data;
  }
}
复制代码

依赖MyInheritedWidget中的数据

class DependWidget extends StatefulWidget {
  @override
  DependWidgetState createState() {
    return new DependWidgetState();
  }
}

class DependWidgetState extends State<DependWidget> {
  @override
  Widget build(BuildContext context) {
    print('build..............');
    // 使用依赖的InheritedWidget中的数据
    return Text(MyInheritedWidget.of(context).data.toString());
  }

  // 当依赖的InheritedWidget中的数据发生变化时,调用此方法
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('didChangeDependencies.....');
  }

  @override
  void didUpdateWidget(DependWidget oldWidget) {
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget");
  }
}
复制代码

当共享数据变化时,依赖发生变化。

import 'package:flutter/material.dart';

class InheritedWidgetTest extends StatefulWidget {
  @override
  _InheritedWidgetTestState createState() => _InheritedWidgetTestState();
}

class _InheritedWidgetTestState extends State<InheritedWidgetTest> {
  int count = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedWidgetTest"),
      ),
      body: Center(
        child: MyInheritedWidget(
          data: count, // setState后  共享数据变化
          child: Center(
            child: DependWidget(), // 共享数据变化,影响该widget
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(
            () {
              ++count; // 数据变化
            },
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}
复制代码

深入源码去理解

1.MyInheritedWidget 中的 of

of有两个作用:

  • 获取MyInheritedWidget的实例。
  • 将DepentWidget添加到MyInheritedWidget的依赖列表。

在MyInheritedWidget中定义of:

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

这个方法的返回值是MyInheritedWidget,显然其目的是获取MyInheritedWidget的实例,需要传入BuildContext参数。调用处在DependWidget中:

@override
Widget build(BuildContext context) {
  // 使用依赖的InheritedWidget中的数据
  return Text(MyInheritedWidget.of(context).data.toString());
}

注意这里的data就是要共享的数据,context传入的是DependWidgetState中build方法的context。

那么of方法是怎么获取到MyInheritedWidget的?

追踪context.dependOnInheritedWidgetOfExactType(),到Element中:

@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;
  }

有两个关键点:

  • final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T]; 根据泛型获取到InheritedElement的实例,注意不是InheritedWidget。
  • return dependOnInheritedElement(ancestor, aspect: aspect) as T;对InheritedElement做一些操作,然后在方法内部将MyInheritedWidget返回出去。

先看_inheritedWidget,这是一个定义在Element中的属性:

Map<Type, InheritedElement> _inheritedWidgets;

保存的内容是:在当前的widget树中,所有的InheritedWidget系的泛型类与InheritedElement的对应关系。这也就是为什么可以通过_inheritedWidgets[T]获取到InheritedElement ancestor的原因,这里的T就是MyInheritedWidget,这里的ancestor其实是MyInheritedWidget对应的InheritedElement。

那么InheritedElement是什么时候进入_inheritedWidgets中的呢?继续查看源码,在element的mount方法内:

void mount(Element parent, dynamic newSlot) {
  。。。。
  _updateInheritance();
  。。。。
}
void _updateInheritance() {
  assert(_active);
  _inheritedWidgets = _parent?._inheritedWidgets;
}

也就是说在Element的时,会通过_updateInheritance方法更新_inheritedWidgets,当前Element的_inheritedWidgets会从parent那里继承过来。但是需要注意,上面的_updateInheritance是默认实现,而InheritedElement重写了该方法:

@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;
  }

首先,先从parent那里获取_inheritedWidgets,如果为null,就创建一个新的;如果不为null就复制一份数据。最后_inheritedWidgets[widget.runtimeType] = this;将自己放进去。

整体来说,每个Element都有一个_inheritedWidgets字段,如果是非InheritedElement,那么_inheritedWidgets从parent那里获取到,如果是InheritedElement,那么_inheritedWidgets先从parent复制一份,然后将自己添加进去。如此一来,在整个Element 的tree中,_inheritedWidgets就层层传递下来了。这也就是为什么前面InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];可以获取MyInheritedWidget的InheritedElement的原因(在MyInheritedWidget的InheritedElement mount时,已经将自己给添加进去了)。

再看dependOnInheritedElement:

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

这里的InheritedElement ancestor是MyInheritedWidget的InheritedElement,所以最后return ancestor.widget返回的是MyInheritedWidget。

这里的ancestor.updateDependencies(this, aspect),this是DependWidget(因为是在DepentWidget中调用的of,context是DepentWidget的build方法的context),继续深入:

@protected
void updateDependencies(Element dependent, Object aspect) {
  setDependencies(dependent, null);
}

@protected
void setDependencies(Element dependent, Object value) {
  _dependents[dependent] = value;
}

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

_dependents是InheritedElement下的一个属性,保存的是:所有使用到MyInheritedWidget的共享数据的Widget。也就是说在这里,将DependWidget放到_dependents内了,即当一个widget通过of来获取InheritedWidget时,就会把自己添加进该InheritedWidget的依赖列表内。

回过头来再看:

  _dependencies ??= HashSet<InheritedElement>();
  _dependencies.add(ancestor);

_dependencies是在Element下定义的一个属性(也就是说,每个继承Element的xxxElement都有该属性):

Set<InheritedElement> _dependencies;
复制代码

由定义可知,其保存的全是InheritedElement,在调用of方法时,就将指定泛型的InheritedElement添加到该set内,后续在notifyClient时需要据此判断DepentWidget是否真的依赖MyInheritedWidget

@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);
    }
  }
}

至此 of 方法做的事情全部完毕。

2.updateShouldNotify的使用

当共享数据发生变化时,根据此方法的返回值来判断是否通知DependWidget进行更新。

@override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
  return oldWidget.data != data;
}

在本例中,变化的起源:

floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(
            () {
              ++count; // 数据变化
            },
          );
        },
        child: Icon(Icons.add),
      ),

setState后发生了什么?

我们之所以可以直接调用setState方法,是因为该方法是定义在State中的:

@protected
  void setState(VoidCallback fn) {
    。。。。。
    _element.markNeedsBuild();
  }

继续, _element.markNeedsBuild(),显然该方法是Element中的:

/// Marks the element as dirty and adds it to the global list of widgets to
/// rebuild in the next frame.
void markNeedsBuild() {
  。。。。。。
  if (dirty)
    return;
  _dirty = true;
  owner.scheduleBuildFor(this);
}

将该element的_dirty标记为true,并添加到全局widgets集合中,以使其在下一帧重新build。在build之前会调用updated方法。注意这里是在_InheritedWidgetTestState中调用的,在构建widget树时,当到InheritedWidget时,build之前会触发InheritedElement的updated方法:

/// Calls [Element.didChangeDependencies] of all dependent elements, if
  /// [InheritedWidget.updateShouldNotify] returns true.
  ///
  /// Called by [update], immediately prior to [build].
  ///
  /// Calls [notifyClients] to actually trigger the notifications.
  @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  }

这里就会判断updateShouldNotify方法的返回值,true继续super调用,继续看super,是ProxyElement:

/// Called during build when the [widget] has changed.
///
/// By default, calls [notifyClients]. Subclasses may override this method to
/// avoid calling [notifyClients] unnecessarily (e.g. if the old and new
/// widgets are equivalent).
@protected
void updated(covariant ProxyWidget oldWidget) {
  notifyClients(oldWidget);
}

/// Notify other objects that the widget associated with this element has
/// changed.
///
/// Called during [update] (via [updated]) after changing the widget
/// associated with this element but before rebuilding this element.
@protected
void notifyClients(covariant ProxyWidget oldWidget);

这里的notifyClients是个空实现,所以我们继续看InheritedElement:

/// Notifies all dependent elements that this inherited widget has changed, by
  /// calling [Element.didChangeDependencies].
  @override
  void notifyClients(InheritedWidget oldWidget) {
    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();
  }

注意这里用到了前面所提的:dependent._dependencies.contains(this);这里的dependent就是我们前面使用到MyInheritedWidget的DependWidget,这样就回调到了使用者的生命周期didChangeDependencies()。

但是,还是没有触发DependWidget的build方法,怎么触发? 注意上面的setState的位置,DependWidget是一个Statefulwidget,这时就会触发DependWidget的didUpdateWidget生命周期方法,进而触发build方法,从而更新了数据。

关键流程总结

1.树的构建过程中都做了什么

  • 每个Widget创建过程中,实际调用对应的Element。
  • Element在添加到渲染树时调用mount方法。
  • mount方法内_updateInheritance方法。
  • 注意Widget是棵树,Element层层穿件,_updateInheritance方法每个Element都会调用,如果当前是非InheritedWidget则继承父类的_inheritedWidgets,如果是InheritedWidget则继承parent的_inheritedWidgets,并将自己添加进去。
  • MyInheritedWidget就把自己放到_inheritedWidgets内了。
  • DependWidget的Element在mount时,就持有了_inheritedWidgets,其中包括MyInheritedWidget(其实是InheritedElement)

2.怎么获取共享数据

  • 在DependWidget的build方法内使用MyInheritedWidget.of(context).data.toString()获取到共享的状态数据
  • MyInheritedWidget的of方法实际调用的是context.dependOnInheritedWidgetOfExactType()
  • context实际就是Element,进而调用到Element的dependOnInheritedWidgetOfExactType()方法,调用InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T],获取到了MyInheritedWidget的InheritedElenent,后续内部继续调用dependOnInheritedElement(),首先_dependencies更新,将InheritedElement添加到Set中,然后ancestor.updateDependencies() - setDependencies() - _dependents[dependent] = value,如此就将DependWidget添加到InheritedElement的依赖列表中。 最后内部调用dependOnInheritedElement中return ancestor.widget;of方法就获取到MyInheritedWidget的示例。

3.数据刷新过程

  • _InheritedWidgetTestState内调用setState
  • 实际是State的setState方法,内部调用Element的markNeedsBuild()方法。
  • markNeedsBuild()内部将Element的 _dirty 标为true,表示下一帧就rebuild。
  • _InheritedWidgetTestState 重新 build。
  • widget树重新构建过程中,在MyInheritedWidget的build前,系统调用updated方法,内部widget.updateShouldNotify(oldWidget),判断是否通知DependWidget更新。如果为true,进而调用到notifyClients方法,notifyClients内部遍历所有的依赖者(DependWidget),逐个调用依赖者的didChangeDependencies方法。
  • widget树重新构建过程中,由于DependWidget是个StatefulWidget,因此不再创建state,而是调用didUpdateWidget方法,继续调用DependWidget的build方法,build内再调用of方法,获取关系的数据,刷新数据。

4.state和element的didChangeDependencies的联系

实我们的element和state的didChangeDependencies方法是完全不同的两个方法,但是在element的didChangeDependencies方法触发更新之后,往往会触发state的didChangeDependencies回调,众所周知,state的didChangeDependencies一般是在firstBuild也就是initState之后会回调,其实还有一个调用时机,就是在InheritedWidget更新之后,只要updateShouldNotify为true,依赖类就会触发,那是怎么触发的呢,我们可以看一下源码,在我们上面分析到触发markNeedsBuild()的时候,渲染流水线触发势必就会触发element的performRebuild回调,在StatefulElement里面performRebuild的实现是

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

在performRebuild回调后会判断_didChangeDependencies这个值是否为true,如果是则会回调_state.didChangeDependencies(),那_didChangeDependencies什么时候为true的呢,

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

其实在之前触发,element的didChangeDependencies方法的时候,_didChangeDependencies就已经为true了,所以只要是element的didChangeDependencies触发,通常都会跟着触发state的didChangeDependencies方法

5.context.dependOnInheritedWidgetOfExactType()和context.findAncestorWidgetOfExactType的区别

context.findAncestorWidgetOfExactType也能找到父节点,那他是怎么实现的呢

  @override
  T findAncestorWidgetOfExactType<T extends Widget>() {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    Element ancestor = _parent;
    while (ancestor != null && ancestor.widget.runtimeType != T)
      ancestor = ancestor._parent;
    return ancestor?.widget as T;
  }

可以看到,这个方法比较粗糙,从当前节点开始,往父节点递归,直到找到runtimeType跟泛型相同的时候直接返回,所以跟context.dependOnInheritedWidgetOfExactType()相比,这个方法的效率会特别低,但是这个方法也并不是毫无用处,因为我们刚才知道context.dependOnInheritedWidgetOfExactType()必须在父节点为InheritedWidget的时候才能找到,但是findAncestorWidgetOfExactType这个方法就抛掉了这个限制,可以直接暴力性的往上搜寻任何一个节点。

猜你喜欢

转载自blog.csdn.net/jdsjlzx/article/details/123304250