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;@overrideWidget 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});@overrideWidget build(BuildContext context) {return TextB(count: count);}}class TextB extends StatefulWidget {final int? count;TextB({this.count});@overrideState<StatefulWidget> createState() {// TODO: implement createStatereturn TextBState();}}class TextBState extends State<TextB> {@overridevoid didChangeDependencies() {print('didChangeDependencies 执行了');super.didChangeDependencies();}@overrideWidget 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?;
其他调用不做修改,我们看一下结果:
那么这两个方法有什么区别呢?我们对比一下这两个方法的源码:
@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;}
我们发现dependOnInheritedWidgetOfExactType方法多调用了dependOnInheritedElement方法,此方法源码如下:
@overrideInheritedWidget 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方法都会执行;
