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