一:InheritedWidget是什么
InheritedWidget是flutter中非常重要的功能性组件,它提供了一种在widget树从上到下共享数据的方式。比如在应用的根widget中通过InheritedWidget共享了一个数据,那么我们便可以在任意子Widget中来获取该共享的数据。
如上图所示,比起普通的widget,逐级传递数据来看,InheritedWidget可以实现子控件跨级传递数据。这个特性在一些需要在整个widget树中共享数据的场景中非常方便,例如Flutter SDK正是通过InheritedWidget来共享应用主题Theme和语言环境Locale。代码上看区别:
当这个跨级跨的越来越大,传递数据越来越多时,InheritedWidget的特性就显得非常重要。
二:InheritedWidget怎么用

通过上面这张图来讲,在ShareDataWidget中,定义了一个data参数,由外部传入,在ShareDataWidget子控件中,ChildWidget2可以通过ShareDataWidget.of(context)?.data
获取到数据。
先来看下InheritedWidget中比较重要的几个方法:
context.dependOnInheritedWidgetOfExactType<ShareDataWidget>()
当在ChildWidget2中,调用该方法时,ChildWidget2和ShareDataWidget就会创建依赖关系,当ShareDataWidget的数据更改了,并且updateShouldNotify方法返回true时,ChildWidget2就会触发didChangeDependencies、build方法。
context.getElementForInheritedWidgetOfExactType<ShareDataWidget>()!.widget as ShareDataWidget
这个方法和第一个方法的区别是,在子控件中调用该方法时,并不会将子控件和ShareDataWidget进行依赖关系绑定,所以当ShareDataWidget的数据更改,updateShouldNotify返回true时,也不会触发该控件的build和didChangeDependencies。(当然要注意写法,后面会说明)

上述代码中,dependOnInheritedElement
方法中主要是注册了依赖关系,之后当InheritedWidget
发生变化时,就会更新依赖他的子组件(调用didChangeDependencies、build方法),如果没有依赖的子组件也不会更新。
bool updateShouldNotify(covariant ShareDataWidget oldWidget)
当ShareDataWidget的data变化了之后,InheritedWidget可以决定是否更新其子控件,当然也可以选择不更新,更新返回true,不更新返回false。
讲的有点抽象,看个例子:
当运行后,打印日志如下:
bash
I/flutter (11723): rebuild common widget:0
I/flutter (11723): rebuild dependOnInheritedWidgetOfExactType widget:1
I/flutter (11723): rebuild getElementForInheritedWidgetOfExactType widget:1
I/flutter (11723): rebuild common widget:1
I/flutter (11723): rebuild dependOnInheritedWidgetOfExactType widget:2
I/flutter (11723): rebuild getElementForInheritedWidgetOfExactType widget:2
I/flutter (11723): rebuild common widget:2
-------------过了3秒后,调用setstate,打印日志如下-----------
I/flutter (11723): rebuild common widget:0
I/flutter (11723): rebuild dependOnInheritedWidgetOfExactType widget:2
I/flutter (11723): rebuild getElementForInheritedWidgetOfExactType widget:2
I/flutter (11723): rebuild common widget:2
I/flutter (11723): rebuild dependOnInheritedWidgetOfExactType widget:1
第一段日志,按照控件顺序,调用了build方法。当过了3秒后,调用了TestState.setState方法后,TestState的build方法被触发。
- getCommonWidget(0):无缓存,触发build,打印日志
- depend1:虽然有缓存,但是由于依赖了ShareDataWidget,且data发生变化,因此触发build。但是build顺序在最后。
- notDepend1:有缓存,但是不依赖ShareDataWidget,因此不触发build。
- common:有缓存,不触发build
- getDependWidget(1):因为依赖ShareDataWidget,data发生变化,触发build。
- getNotDependWidget(2)、getCommonWidget(2):不依赖ShareDataWidget, 且没有缓存,触发build。
对上述日志进行总结就是:
- 父控件(TestState)的setState会造成build触发,触发TestState内的全局刷新
- 如果子控件无缓存,每次父控件build都会触发子控件build。(getCommonWidget(0)、getDependWidget(1)、getNotDependWidget(2)、getCommonWidget(2))
- 如果子控件有缓存,但是依赖了InheritedWidget,且数据发生变化,则触发build(depend1)。若不依赖InheritedWidget,则不触发build(common、notDepend1)。
所以如果是不依赖InheritedWidget的子widget,需要有缓存,否则还是会触发build。
三:InheritedWidget进一步优化
上述代码的例子中,如果TestState.data变化,我们只想更新依赖了ShareDataWidget的子控件,而现在更新data字段,需要调用TestState.setState方法,会导致没有缓存的子节点都被重新build,这很没有必要。解决办法就是缓存,但是我们平时写代码,不可能像上面示例代码中那样,在State中声明这样的控件去缓存,一个简单的办法就是,通过封装一个StatefulWidget,将InheritedWidget(ShareDataWidget)封装起来。
优化前 | 优化后 |
---|---|
![]() |
![]() |
优化前,页面widget给InheritedWidget传入data,更新的时候调用页面Widget的setState,会造成未缓存的子widget都刷新。 | 用一个新封装widget来封装InheritedWidget,且是唯一的子组件。页面Widget给新封装widget传入T data和Widget child(图中蓝色的子widget),当页面Widget发现data变化时,通知新封装widget调用setState,重新构建InheritedWidget。 |
优化点在于:
- setState范围缩小:data变化,不会调用页面Widget.setState,降低build成本,淡橙色的子widget都不会受影响。只会调用新封装widget.setState,只会重新构建InheritedWidget。
- InheritedWidget子组件缓存:由于InheritedWidget都是页面Widget传入缓存在新封装widget里的,因此当InheritedWidget重建时,也只会重新构建依赖的子组件,不依赖的子组件则不rebuild。
那么问题来了,data变化的时候,页面Widget如何通知新封装Widget呢?当然实现的方式有很多种,比如ChangeNotifier,让T data
继承ChangeNotifier,然后在数据更改的时候进行通知。当把data传入到新封装Widget中后,在initState里,给data添加listener,去监听数据变化。
scala
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
ChangeNotifierProvider({Key? key, this.data, this.child});
final Widget child;
final T data;
//定义一个便捷方法,方便子树中的widget获取共享数据
static T of<T>(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<InheritedProvider<T>>().data;
}
@override
_ChangeNotifierProviderState<T> createState() => _ChangeNotifierProviderState<T>();
}
class _ChangeNotifierProviderState<T extends ChangeNotifier>
extends State<ChangeNotifierProvider<T>> {
@override
void initState() {
// 给model添加监听器
widget.data.addListener(update);
super.initState();
}
@override
void didUpdateWidget(ChangeNotifierProvider<T> oldWidget) {
//当Provider更新时,如果新旧数据不"==",则解绑旧数据监听,同时添加新数据监听
if (widget.data != oldWidget.data) {
oldWidget.data.removeListener(update);
widget.data.addListener(update);
}
super.didUpdateWidget(oldWidget);
}
void update() {
//如果数据发生变化(model类调用了notifyListeners),重新构建InheritedProvider
setState(() => {});
}
@override
Widget build(BuildContext context) {
return InheritedProvider<T>(
data: widget.data,
child: widget.child,
);
}
@override
void dispose() {
// 移除model的监听器
widget.data.removeListener(update);
super.dispose();
}
}
// 一个通用的InheritedWidget,保存需要跨组件共享的状态
class InheritedProvider<T> extends InheritedWidget {
InheritedProvider({required this.data, required Widget child});
final T data;
@override
bool updateShouldNotify(InheritedProvider<T> old) {
//在此简单返回true,则每次更新都会调用依赖其的子孙节点的`didChangeDependencies`。
return true;
}
}
再画个更清楚的流程图,当然画的不全,比如removeListener这些都没画进去,就是大概表示一下意思:

讲了这么多,看一下最终代码使用的例子:
less
class _ProviderRouteState extends State<ProviderRoute> {
@override
Widget build(BuildContext context) {
return Center(
child: ChangeNotifierProvider<CartModel>(
data: CartModel(),
child: Builder(builder: (context) {
return Column(children: <Widget>[
Builder(builder: (context) {
var cart = ChangeNotifierProvider.of<CartModel>(context);
return Text("总价: ${cart.totalPrice}");
}),
Builder(builder: (context) {
print("ElevatedButton build"); //在后面优化部分会用到
return ElevatedButton(
child: Text("添加商品"),
onPressed: () {
//给购物车中添加商品,添加后总价会更新
ChangeNotifierProvider.of<CartModel>(context)
.add(Item(20.0, 1));
});
})
]);
})));
}
}
上面这个代码中,添加商品的按钮,每次点击后,会导致CardModel刷新,但是由于按钮自身依赖了InheritedWidget,所以也会导致rebuild,这里可以优化一下,让其不依赖。
至此上面讲的,基本上是Provider(一个用于管理状态的包)的底层原理。
上面的例子看似简单,不能体现Provider的强大,但是如果当我们的业务变得很复杂,一个页面内部层级比较深,状态比较多,各个子组件不断嵌套,那么如果要逐级传递数据的话,就会显得不那么优雅,用Provider就能很好的解决跨级传递数据问题。如果在App内,是多个页面共享数据的话,那么则需要将Provider设置的层级更高一些,比如在main.dart中。
Flutter社区还有其他用于状态管理的包,例如:Scoped Model、Redux、MobX、BLoC。等我一一研究再分享。
上述描述有疏漏的,请大家指正。