Flutter(三十三)-InheritedWidget数据共享
InheritedWidget是Flutter中非常重要的一个功能型组件,它提供了一种在Widget树中从上到下共享数据的方式,比如我们在一个应用的根Widget中通过InheritedWidget共享了一个数据,那么在这个应用的任意一个子Widget中都可以获取到该数据!在Flutter中的应用主题Theme和当前语言Locale正是通过InheritedWidget来共享信息的;
InheritedWidget
didChangeDependencies
我们在之前介绍StatefulWidget的生命周期时,曾经提到过State对象的didChangeDependencies回调方法,它会在依赖发生改变是被Flutter框架调用。这个"依赖"指的是子Widget是否使用了父Widget中InheritedWidget的数据!如果使用了,则代表有依赖关系,否则没有依赖关系;这种机制可以使子组件在所依赖的InheritedWidget发生变化是来更新自身!
没有依赖关系
class InheritedDemo extends StatefulWidget {
@override
_InheritedDemoState createState() => _InheritedDemoState();
}
class _InheritedDemoState extends State<InheritedDemo> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Row(
children: [
Container(child: TextA(count: _count), width: 60, height: 30, alignment: Alignment.center,),
ElevatedButton(onPressed: () {
setState(() {
_count++;
});
}, child: const Icon(Icons.add))
],
);
}
}
class TextA extends StatelessWidget {
final int? count;
TextA({this.count});
@override
Widget build(BuildContext context) {
return TextB(count: count);
}
}
class TextB extends StatefulWidget {
final int? count;
TextB({this.count});
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return TextBState();
}
}
class TextBState extends State<TextB> {
@override
void didChangeDependencies() {
print('didChangeDependencies 执行了');
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
print('build 执行了');
return Text('${widget.count}');
}
}
我们看如上这段代码,在InheritedDemo中添加了一个TextA和ElevatedButton,点击按钮时改变界面上Text的值,但是TextA并不显示Text,而是将count传给了TextB,然后由TextB来显示Text,此时我们点击按钮,查看效果:

我们发现didChangeDependencies方法在项目运行之后,在build执行之前执行了一次,之后我们点击按钮,此方法没有再执行;也就是当前TextB与InheritedDemo之间并没有依赖关系;
有依赖关系
我们来创建一个InheritedData继承InheritedWidget,其实现如下:

InheritedData需要集成自InheritedWidget;构造方法中必须添加required Widget child,后边必须跟上super(child: child);子Widget通过of方法来获取共享数据;- 必须重写
updateShouldNotify方法,方法返回true时,当前共享数据的clickCount发生改变时,将会通知子Widget中依赖clickCount的Widget(此时didChangeDependencies和build方法都会调用);否则不会通知(此时只会调用build方法);
我们将原来的代码中InheritedDemo代码修改如下:

其build方法中使用InheritedData,将原来的Row放进InheritedData的child属性中,将_count赋值给InheritedData的clickCount属性;
将TextB的代码修改如下:

最终在TextB中的build方法中,引用InheritedData的数据给Text赋值;
最终运行及打印结果如下:

didChangeDependencies方法执行了,说明存在依赖关系;
didChangeDependencies中可以做什么
一般来说,很少会在子Widget中重写此方法,因为在依赖改变后,Flutter框架也会调用build()方法重新构建树。但是如果需要在依赖发生改变后执行一些昂贵或者耗时的操作,比如网络请求,这时候最好的方法就是在didChangeDependencies方法中执行操作,这样可以避免每次build()都执行这些耗时操作;
深入了解InheritedWidget
如果我们指向要在TextB中引用InheritedData的数据,但是不希望在InheritedData发生改变时调用TextBState的didChangeDependencies方法,那么我们应该如何做呢?
我们可以将InheritedData的of方法修改如下:
static InheritedData of(BuildContext context) {
final InheritedData? result = context.getElementForInheritedWidgetOfExactType<InheritedData>()!.widget as InheritedData?;
assert(result != null, 'No InheritedData found in context');
return result!;
}
在of方法中,将获取InheritedData对象的方式从原来的:
final InheritedData? result = context.dependOnInheritedWidgetOfExactType<InheritedData>();
修改为:
final InheritedData? result = context.getElementForInheritedWidgetOfExactType<InheritedData>()!.widget as InheritedData?;
其他调用不做修改,我们看一下结果:

那么这两个方法有什么区别呢?我们对比一下这两个方法的源码:
@override
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
return ancestor;
}
@override
InheritedWidget 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;
}
我们发现dependOnInheritedWidgetOfExactType方法多调用了dependOnInheritedElement方法,此方法源码如下:
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
在dependOnInheritedElement方法中主要是注册了依赖关系!所以在调用dependOnInheritedWidgetOfExactType方法时,InheritedWidget和依赖它的子部件关系将会注册完成,之后当InheritedWidget发生变化时,就会更新组件的依赖关系,也就是会调用子部件的didChangeDependencies方法和build方法。
需要注意的是,虽然将调用方式改为getElementForInheritedWidgetOfExactType方法之后,didChangeDependencies方法虽然不会执行,但是build方法依然会执行!这是因为当我们点击按钮时,调用了_InheritedDemoState的setState方法,此时将会重新构建整个页面,由于TextA和TextB并没有任何缓存,所以他们都会被重新构建,因此TextA和TextB的build方法都会执行;
转载自:https://juejin.cn/post/7054344539045117989