Flutter 源码解读 - InheritedWidget

365 阅读2分钟

Flutter 状态管理源码解读 - scoped_model 这篇文章中我们讲到 InheritedWidget是一个非常重要的功能型组件,它提供了一种在 Widget 树中从上到下共享数据的方式。其实很多状态管理框架,比如 BlocReduxProvider 等都是使用了 InheritedWidget,那我们今天就一起来学习一下它是如何进行工作的。

一、InheritedWidget 的简单使用

InheritedWidget Widget 允许它的子 Widget 访问父 Widget 中的数据,使用它可以省去在 Widget 之间传递的数据的麻烦,任意的子 Widget 都可以共享这些数据。

下面我们先看一个一个计数器的小例子,体验一下它的魅力。

首先编写一个继承自 InheritedWidget 的类,代码如下:

class InheritedInfoWidget extends InheritedWidget {
  InheritedInfoWidget({
    required this.number,
    required this.child,
    Key? key,
  }) : super(
          key: key,
          child: child,
        );

  final int number;
  final Widget child;

  @override
  bool updateShouldNotify(covariant InheritedInfoWidget oldWidget) {
    return oldWidget.number != number;
  }

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

然后再编写一个 InfoChildWidget 来展示数字,代码如下:

class InfoChildWidget extends StatelessWidget {
  const InfoChildWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final int number = InheritedInfoWidget.of(context)!.number;
    return Text('$number');
  }
}

最后,编写 InheritedDemoPage 组件,来进行承载,代码如下:

class InheritedDemoPage extends StatefulWidget {
  const InheritedDemoPage({Key? key}) : super(key: key);

  @override
  State<InheritedDemoPage> createState() => _InheritedDemoPageState();
}

class _InheritedDemoPageState extends State<InheritedDemoPage> {
  int _number = 0;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('inherited page demo'),
        centerTitle: true,
      ),
      body: InheritedInfoWidget(
        number: _number,
        child: Center(
          child: InfoChildWidget(),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: Icon(
          Icons.add,
        ),
      ),
    );
  }
}

对,你没有看错,就是这么简单,从上面的代码我们可以看出,InheritedDemoPage State 中 的 _number 变量值在 InfoChildWidget 中通过 InheritedInfoWidget.of(context)!.number 可以直接使用,而无需通过后传递参数。当然如果我们通过传递参数的话也是可以达到以上效果的,本例主要是为了演示 InheritedWidget 的使用。

此时我们把 InfoChildWidget 改成 StatefulWidget ,来看一下,代码如下:

class InfoChildWidget extends StatefulWidget {
  const InfoChildWidget({Key? key}) : super(key: key);

  @override
  State<InfoChildWidget> createState() => _InfoChildWidgetState();
}

class _InfoChildWidgetState extends State<InfoChildWidget> {
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
    //如果build中没有依赖InheritedWidget,则此回调不会被调用。
    print('===== Dependencies change======');
  }

  @override
  Widget build(BuildContext context) {
    final int number = InheritedInfoWidget.of(context)!.number;
    return Text('$number');
    // return Text('number');
  }
}

此时每当我当我们点击一下底部 ➕,didChangeDependencies 便会调用,如图:

image.png

Flutter 源码阅读 - StatefulWidget 源码分析 & State 生命周期 文章中我们也已经介绍过,当 State 对象的依赖发生变化时会进行调用,而这个“依赖”指的就是子 widget 是否使用了父 widget 中 InheritedWidget 的数据。如果使用了,则代表子 Widget 有依赖;如果没有使用则代表没有依赖。这种机制可以使子组件在所依赖的 InheritedWidget 变化时来更新自身,例如:例如系统语言 Locale 或者应用主题等。

那么如果我们把 build 中改成如下代码,didChangeDependencies 便不会再调用,因为它并没有依赖 InheritedInfoWidget 中的数据:

Widget build(BuildContext context) {
    return Text('number');
}

二、深入 InheritedWidget

如果继续我们把 InheritedInfoWidget 中的代码改成如下:

static InheritedInfoWidget? of(BuildContext context) {
       return context
        .getElementForInheritedWidgetOfExactType<InheritedInfoWidget>()
        ?.widget as InheritedInfoWidget;
}

此时点击 ➕ 发现 _InfoChildWidgetState#didChangeDependencies 也不会执行,那这是为什么呢? 唯一的改动就是 InheritedInfoWidget 对象的获取方式,把 context.dependOnInheritedWidgetOfExactType<InheritedInfoWidget>() 改成了 context .getElementForInheritedWidgetOfExactType<InheritedInfoWidget>()?.widget as InheritedInfoWidget。那么我们就深入源码来进行分析一下。

image.png

我们可以看出 dependOnInheritedWidgetOfExactType()getElementForInheritedWidgetOfExactType() 多调了 dependOnInheritedElement 方法。dependOnInheritedElement 主要就是注册了依赖关系。所以在调用dependOnInheritedWidgetOfExactType() 时,InheritedWidget 和依赖它的子孙组件关系便完成了注册,之后当 InheritedWidget 发生变化时,就会更新依赖它的子孙组件,也就是会调这些子孙组件的didChangeDependencies() 方法和 build() 方法。而当调用的是 getElementForInheritedWidgetOfExactType() 时,由于没有注册依赖关系,所以之后当InheritedWidget 发生变化时,就不会更新相应的子孙 Widget。