最近因为flutter项目写的有些上头,所以关于flutter的更新会相对多一些。今天来分享一下flutter的数据共享组件inheritedwidget,它为flutter提供了一种在widget树中可以从上到下进行数据共享的选择,帮助我们实现在任意子widget中获取所有共享数据的诉求,最为常见的使用场景有共享主题和国际化的local处理。
didChangeDependencies
提及didChangeDependencies就需要聊一下widget的生命周期,关于widget的生命周期可以大致分为:
·initstate:当widget第一次插入到widger树时被调用,对于每个state对象,flutter只调用一次该处理。
·didChangeDependencies:当state对象的依赖发生变化时被调用,如当前系统语言local或主题发生变化时,flutter框架层会通知widget调用该回调。
·build:用于构建widget子树。
·reassemle:热重载时调用。
·didUpdateWidget:widget重新构建时调用。
·deactivate:当state对象从树中移除时调用。
·dispose:state对象永久从树中移除时调用。
对于didChangeDependencies相关的依赖,实际就是子widget是否使用了父widget中的inheritedwidget的数据,使用即为有依赖关系,这种机制可以使子组件在所依赖的inheritedwidget发生变化时进行自身更新,让我们来看一下实例代码:
class ShareDataWidget extends InheritedWidget {
ShareDataWidget({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);
final int data; //需要在子树中共享的数据,保存点击次数
//定义一个便捷方法,方便子树中的widget获取共享数据
static ShareDataWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
}
//该回调决定当data发生变化时,是否通知子树中依赖data的Widget
@override
bool updateShouldNotify(ShareDataWidget old) {
return old.data != data;
}}
shareddatawidget继承inheritedwidget,使用data保存共享数据,然后实现子组件_testwidget,在其build方法中饮用shareddatawidget中的数据,在_testwidget的didChangeDependencies打印日志:
class _TestWidget extends StatefulWidget {
@override
__TestWidgetState createState() => __TestWidgetState();}class __TestWidgetState extends State<_TestWidget> {
@override
Widget build(BuildContext context) {
//使用InheritedWidget中的共享数据
return Text(ShareDataWidget.of(context)!.data.toString());
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
//父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
//如果build中没有依赖InheritedWidget,则此回调不会被调用。
print("Dependencies change");
}}
接下来创建一个路由页面,包含一个按钮,点击时实现shareddatewidget的值自增:
class InheritedWidgetTestRoute extends StatefulWidget {
@override
_InheritedWidgetTestRouteState createState() => _InheritedWidgetTestRouteState();}class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
int count = 0;
@override
Widget build(BuildContext context) {
return Center(
child: ShareDataWidget( //使用ShareDataWidget
data: count,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: _TestWidget(),//子widget中依赖ShareDataWidget
),
RaisedButton(
child: Text("Increment"),
//每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新
onPressed: () => setState(() => ++count),
)
],
),
),
);
}}
运行后可以发现,当子组件_TestWidget中依赖父组件ShareDataWidget的数据data发生变化时,子组件的didChangeDependencies回调会被调用,关键代码在于return Text(ShareDataWidget.of(context)!.data.toString());
一般来说作为子widget很少需要重写didChangeDependencies,因为在依赖发生改变后flutter框架自身也会调用build重新构建widget树,在此之外,如果我们需要执行一些消耗资源较多的操作,如网络请求时,可以在此回调中执行,以避免每次build都要执行一次资源开销;
关于InheritedWidget
前面我们有发现子组件建立与父组件的依赖关系是由于代码行Text(ShareDataWidget.of(context)!.data.toString()),那么有没有办法在子组件依赖的父组件数据发生变化时,子组件不执行didChangeDependencies回调呢?对于这类问题,我们可以考虑从获取父组件对象的方式替换考虑,将实例中父组件ShareDataWidget为其子组件提供获取数据的方法接口由
context.dependOnInheritedWidgetOfExactType<ShareDataWidget>()
调整为context.getElementForInheritedWidgetOfExactType<ShareDataWidget>().widget即可,那么二者的区别在于何处?让我们来看一下具体实现:
@overrideInheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
return ancestor;}
@overrideInheritedWidget dependOnInheritedWidgetOfExactType({ Object aspect }) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
//多出的部分
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;}
可以看到后者多加了dependOnInheritedElement处理:
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
可见其中进行了依赖关系绑定,所以在调用dependOnInheritedWidgetOfExactType时,InheritedWidget
和依赖它的子孙组件关系便完成了注册。
关于缓存的使用
上述由ShareDataWidget.of()改为getElementForInheritedWidgetOfExactType()时,点击子组件的increment按钮会发_TestWidgetState的didChangeDependencies()方法不会再被调,但是它的build()仍在调起,原因是由于点击increment后,调用其本身_InheritedWidgetTestRouteState的setstate重构整个页面,由于_testwidget没有缓存,所以也会被重构建调起build(),那么这个问题场景里有没有办法能达到只更新重构子树中与shareddatawidget建立依赖关系的组件,而不是全部widget子节点重构?这是可以考虑使用封装一个statefulwidget将子widget树缓存起来作为缓存。
对于widget的缓存,不得不提及provider,因为篇幅关系,让我们把关于provider的分享留下下一期进行,今天的分享就到这里,感谢各位看官支持,如果有任何疑问或者新的想法,欢迎随时私信雷记讨论,让我们下回继续分解!




