Flutter 3.16.5 • channel stable • github.com/flutter/flu...
Framework • revision 78666c8dc5 (3 months ago) • 2023-12-19 16:14:14 -0800
Engine • revision 3f3e560236
Tools • Dart 3.2.3 • DevTools 2.28.4
本篇为Flutter基建的第十一篇文章💪🏻💪🏻💪🏻,本系列也即将进入尾声了(后续会逐步以一些日常实例和源码相关知识为主),本篇会带着小伙伴们一起了解下Flutter中状态管理相关知识,主要围绕着State和InheritedWidget知识点切入,下面我们一起进入文章感受下吧~
Flutter基建系列文章
Flutter基建 - 形影不离的State**Widget
SetState管理状态
SetState方式是Flutter中最基础的状态管理方式了,在我们每次创建一个新项目的时候,都会看到官方的示例中包含了SetState用法,也就是计数器Demo,下面介绍所有的状态管理代码都采用计时器方式,来一起更深入的了解下Flutter中状态管理的几种方式。
scala
class MySetStateWidget extends StatefulWidget {
const MySetStateWidget({super.key});
@override
State<StatefulWidget> createState() {
return _MySetState();
}
}
class _MySetState extends State<MySetStateWidget> {
int _count = 0;
void _incrementCount(_) {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return buildScaffold(
context,
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkWell(
onTap: () {
_incrementCount(null);
},
child: Text("count: $_count", style: commonTS()),
),
],
),
);
}
}
这是一个最简单最常用的例子,点击按钮_count
值就会自增,这块相信小伙伴们都是一眼就懂的,下面我们将例子稍微增加点,增加一个子Widget,同时将_count
传递过去,并且提供一个自增方法的回调,让子Widget也可以改变我们的状态。
scala
children: [
InkWell(
onTap: () {
_incrementCount(null);
},
child: Text("count: $_count", style: commonTS()),
),
MySetStateSecWidget(_count, _incrementCount),
],
class MySetStateSecWidget extends StatelessWidget {
final int count;
final ValueChanged incrementCount;
const MySetStateSecWidget(this.count, this.incrementCount, {super.key});
@override
Widget build(BuildContext context) {
return Center(
child: InkWell(
onTap: () {
incrementCount(null);
},
child: Text("sec count: $count", style: commonTS()),
),
);
}
}
我们在Column
中添加了一个MySetStateSecWidget
,此Widget引用了MySetStateWidget
的_count
值,并且在其内部的文本点击事件中回调给父Widget,父Widget接受到点击事件之后也是调用了自身的_count
自增方法,这时候我们看到的画面就是无论点击哪个文本,都是可以改变_count
的值,而且文本内容也随之改变。
看到此处大家是否在想,此时两个Widget处于同一个页面,只是在层级方面属于父子关系,这时候可以使用SetState方法来管理状态,如果MySetStateSecWidget
是跳转之后的页面,此时还可以做到这种效果嘛?答案是否定的,_count
是MySetStateWidget
内部的状态,可以传递给Sec,但是此时在Sec页面通过回调改变状态时,Sec的文本则不会随之而改变了,因为它不会重建了~
下面我们来接触下InheritedWidget,看看InheritedWidget是否能达到我们需要的效果呢?
InheritedWidget共享状态
InheritedWidget字面意思为继承的组件,它可以帮助我们在组件树中共享一些状态,下面我们还是以计数器的例子来使用InheritedWidget实现一次。
首先我们定义一个InheritedWidget的子类,内部包含一个count状态值,此状态值可以共享给子Widget。
scala
class CountInheritedModel extends InheritedWidget {
final int count;
const CountInheritedModel(this.count, {required Widget child, Key? key}) : super(child: child, key: key);
@override
bool updateShouldNotify(covariant CountInheritedModel oldWidget) {
return count != oldWidget.count;
}
static CountInheritedModel? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<CountInheritedModel>();
}
}
这里先留意一下静态方法of(context),暂时不考虑具体的内部实现,先知道它就是为了获取当前CountInheritedModel的一个静态方法而已,接下来我们会详细介绍内部的实现逻辑。
定义完InheritedWidget之后,我们再来实现计时器页面~
scala
class CountInheritedWidget extends StatefulWidget {
const CountInheritedWidget({super.key});
@override
State<StatefulWidget> createState() {
return _CountInheritedState();
}
}
class _CountInheritedState extends State<CountInheritedWidget> {
int _count = 1;
@override
Widget build(BuildContext context) {
return buildScaffold(
context,
CountInheritedModel(
_count,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('Inherited Count: $_count'),
const CountInheritedChildWidget(),
],
),
),
actionButton: FloatingActionButton(
onPressed: () {
setState(() {
_count++;
});
},
child: const Icon(Icons.add),
),
);
}
}
class CountInheritedChildWidget extends StatelessWidget {
const CountInheritedChildWidget({super.key});
@override
Widget build(BuildContext context) {
final count = CountInheritedModel.of(context)?.count ?? 0;
return Center(
child: Text("Child Count: $count"),
);
}
}
CountInheritedModel的child种包含了一个简单的文本,用于显示自身_count的值,另外一个则是CountInheritedChildWidget,此组件则是通过CountInheritedModel的静态方法of()来获取count值,下面我们来运行看看能达到一个什么样的效果。
通过上方的GIF可以看出,当我们点击FloatingActionButton的时候,两个Text显示的文本都会随之改变,这就是InheritedWidget给我们带来的共享状态之处,当我们在子Widget中需要共享状态时,无须像SetState那种传参进来,只需要通过CountInheritedModel的静态方法of()来获取共享值即可。
了解了InheritedWidget共享状态使用之后,接下来我们重点了解下of()这个方法内部的使用。
在上面的代码中我们采用的是context.dependOnInheritedWidgetOfExactType()
方法来获取当前的InheritedWidget对象,其实还有另外一种方法也可以帮助我们获取到当前的InheritedWidget对象,就是context.getInheritedWidgetOfExactType()
方法,两种方法都可以帮助我们在任何子组件中获取到共享组件对象,那么二者有何区别呢?
最后我们来分析下具体的实现原理,直接进入源码查看:
dart
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedElements == null ? null : _inheritedElements![T];
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
@override
T? getInheritedWidgetOfExactType<T extends InheritedWidget>() {
return getElementForInheritedWidgetOfExactType<T>()?.widget as T?;
}
@override
InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedElements == null ? null : _inheritedElements![T];
return ancestor;
}
仔细看二者的实现有共同之处,其实dependOnInheritedWidgetOfExactType()方法包含了getElementForInheritedWidgetOfExactType()逻辑,它们都是先从_inheritedElements对象中通过泛型值T来获取到对应的Element对象,此泛型T就是InheritedWidget类型,然后通过Element对象取出widget,只是dependOnInheritedWidgetOfExactType()方法比getInheritedWidgetOfExactType()多了一步dependOnInheritedElement()方法的调用,那我们只需要进入到此方法就可以得出他们的不同之处。
typescript
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
_dependencies ??= HashSet<InheritedElement>();
_dependencies!.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget as InheritedWidget;
}
@protected
void updateDependencies(Element dependent, Object? aspect) {
setDependencies(dependent, null);
}
@protected
void setDependencies(Element dependent, Object? value) {
_dependents[dependent] = value;
}
其实dependOnInheritedElement内部的逻辑也是比较简单的,就是将Element存入到_dependencies对象中,_dependencies对象是一个Set集合,然后再调用ancestor对象的updateDependencies()方法将Element和aspect存到_dependents对象中,这个对象则是一个Map集合,它内部保存的都是与当前Element有依赖关系的组件,存入到其中的作用就是为了当状态改变时,可以直接通知所有有依赖关系的组件进行状态刷新。
看到这里大家是否觉得豁然开朗了,dependOnInheritedWidgetOfExactType()和getInheritedWidgetOfExactType()共同点就是为了获取共享InheritedWidget对象,而dependOnInheritedWidgetOfExactType()方法还会进行组件之间依赖关系的注册,那最后我们将计数器代码换成getInheritedWidgetOfExactType()方式看看效果。
通过具体的效果我们就可以很清楚的看出,通过getInheritedWidgetOfExactType()的方法子组件不会进行状态的实时更新,只会再初始的时候拿一次状态值,这就是getInheritedWidgetOfExactType()和dependOnInheritedWidgetOfExactType()最大的不同之处了。
写在最后
本篇文章主要介绍了Flutter中setState和InheritedWidget共享状态的基本使用,希望通过文章给阅读的小伙伴们带来一点帮助,后续会循序渐进逐步接触Flutter更多的知识。
我是Taonce,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢😆😆~