重中之重, 源码解析: (state的demo和源码分析重点看看)
目录
- StatelessWidget 与 StatefulWidget 区别? build方法
- 原始setstate的原理,状态管理的重要性 (重点)
- 官方 provider的原理解析 (重点)
3.1重点的2种方式和源码分析 , (provider和InheritedWidget)
3.2Provider的案例使用ChangeNotifier, ChangeNotifierProvider 实现2个组件之间的通信
3.3Consumer 刷新指定区域
3.4 Selector的用法
3.5MultiProvider 多zhuang状态共享.
事实上,当我们使用 Provider 后,我们就再也不需要使用 StatefulWidget 了。 - 手写简单版provider
- 基础的跨组件框架, InheritedWidget、Notification和EventBus
- 第三方框架图文比较, 咸鱼Fish Redux的框架 , getX的状态管理
状态管理是什么:
jietu-1706527334684.jpgFlutter的状态可以分为全局状态和局部状态两种。
Flutter 状态管理是指在 Flutter 应用中有效地管理应用的数据和状态
状态管理是声明式编程非常重要的一个概念
问题: 为什么要做状态管理?
就是有几个页面, 要实现数据的同步或者共享!
下面是官方给出的一些原则可以帮助你做决定:
- 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父 Widget 管理。
- 如果状态是有关界面外观效果的,例如颜色、动画,那么状态最好由 Widget 本身来管理。
- 如果某一个状态是不同 Widget 共享的则最好由它们共同的父 Widget 管理。
1. StatelessWidget 与 StatefulWidget 区别?
StatelessWidget和StatefulWidget的区别就在这个可变的State了。
当状态数据发生变化时,Flutter 会调用 build() 方法重新构建界面
问题: build方法什么情况下被执行呢?:
- 1)、当我们的StatelessWidget第一次被插入到Widget树中时(也就是第一次被创建时);
- 2)、当我们的父Widget(parent widget)发生改变时,子Widget会被重新构建;
- 3)、如果我们的Widget依赖InheritedWidget的一些数据,InheritedWidget数据发生改变时;
Stateful widget特有:
至少由两个类组成:
一个StatefulWidget类。
一个 State类; StatefulWidget类本身是不变的,但是State类中持有的状态在 widget 生命周期中可能会发生变化。
问题: 为什么要将 build 方法放在 State 中,而不是放在StatefulWidget中?
1).状态访问不便, 属性会被公开
试想一下,如果我们的StatefulWidget
有很多状态,而每次状态改变都要调用build
方法,由于状态是保存在 State 中的,如果build
方法在StatefulWidget
中,那么build
方法和状态分别在两个类中,那么构建时读取状态将会很不方便!
试想一下,如果真的将build
方法放在 StatefulWidget 中的话,由于构建用户界面过程需要依赖 State,所以build
方法将必须加一个State
参数,大概是下面这样:
Widget build(BuildContext context, State state){
//state.counter
...
}
这样的话就只能将State的所有状态声明为公开的状态,这样才能在State类外部访问状态!但是,将状态设置为公开后,状态将不再具有私密性,这就会导致对状态的修改将会变的不可控。但如果将build()方法放在State中的话,构建过程不仅可以直接访问状态,而且也无需公开私有状态,这会非常方便。
2.继承StatefulWidget不便。
例如,Flutter 中有一个动画 widget 的基类AnimatedWidget,它继承自StatefulWidget类。AnimatedWidget中引入了一个抽象方法build(BuildContext context),继承自AnimatedWidget的动画 widget 都要实现这个build方法。现在设想一下,如果StatefulWidget 类中已经有了一个build方法,正如上面所述,此时build方法需要接收一个 State 对象,这就意味着AnimatedWidget必须将自己的 State 对象(记为_animatedWidgetState)提供给其子类,因为子类需要在其build方法中调用父类的build方法,代码可能如下:
问题: 为什么flutter在设计的时候statefulWidget的build方法放在state中?
- build依赖state中的变量
2.widget会不停的销毁 - 专题改变, 不希望把state改变
问题: build的context是什么
在StatelessElement中,我们发现是将this传入,所以本质上BuildContext就是当前的Element
context是当前 widget 在 widget 树中位置中执行”相关操作“的一个句柄(handle),比如它提供了从当前 widget 开始向上遍历 widget 树以及按照 widget 类型查找父级 widget 的方法
class ContextRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Context测试"),
),
body: Container(
child: Builder(builder: (context) {
// 在 widget 树中向上查找最近的父级`Scaffold` widget
Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();
// 直接返回 AppBar的title, 此处实际上是Text("Context测试")
return (scaffold.appBar as AppBar).title;
}),
),
);
// 查找父级最近的Scaffold对应的ScaffoldState对象
ScaffoldState _state = context.findAncestorStateOfType<ScaffoldState>()!;
如果 StatefulWidget 的状态是希望暴露出的,应当在 StatefulWidget 中提供一个of 静态方法来获取其 State 对象,开发者便可直接通过该方法来获取;如果 State不希望暴露,则不提供of方法
// 直接通过of静态方法来获取ScaffoldState
ScaffoldState _state=Scaffold.of(context);
StatelessWidget特有
问题: 我之前说过定义到Widget中的数据都是不可变的,必须定义为final,为什么呢?
Flutter如何做到我们在开发中定义到Widget中的数据一定是final的呢?
@immutable
abstract class Widget extends DiagnosticableTree {
// ...省略代码
}
这里有一个很关键的东西@immutable
-
我们似乎在Dart中没有见过这种语法,这实际上是一个
注解
,这设计到Dart的元编程,我们这里不展开讲; -
说明: 被@immutable注解标明的类或者子类都必须是不可变的
2. 原始setstate的原理,状态管理的重要性
源码分析:
setState
仅在本地范围内有效,如果一个 Widget
需要改变它自己的状态,那么 setState
就是你最好的选择
setstate() 主要用于修改数据,变量值的! 相当于notifychange
问题: 如何从statefullWidget把数据传递到state类中?
可以2次传递
还有一种方法,state中可以直接取到statefullWidget的实例_widget , 就可以!
之前最简单的就是 widget, setstate
那 State 是在哪里被创建的?
class StatefulElement extends ComponentElement {
StatefulElement(StatefulWidget widget)
: _state = widget.createState(),
super(widget) {
assert(() {
if (!state._debugTypesAreRight(widget)) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('StatefulWidget.createState must return a subtype of State<${widget.runtimeType}>'),
ErrorDescription(
'The createState function for ${widget.runtimeType} returned a state '
'of type ${state.runtimeType}, which is not a subtype of '
'State<${widget.runtimeType}>, violating the contract for createState.',
),
]);
}
return true;
}());
assert(state._element == null);
state._element = this;
state._widget = widget;
assert(state._debugLifecycleState == _StateLifecycle.created);
}
更新ui, state是啥?
@override
void update(ProxyWidget newWidget) {
final ProxyWidget oldWidget = widget as ProxyWidget;
assert(widget != newWidget);
super.update(newWidget);
assert(widget == newWidget);
updated(oldWidget);
rebuild(force: true);
}
调用widget的setstate方法没, 会执行StatefulElement的update()方法
@protected
void setState(VoidCallback fn) {
assert(() {
final Object? result = fn() as dynamic;
}());
_element!.markNeedsBuild(); // markNeedsBuild ()
}
void markNeedsBuild() {
assert(_lifecycleState != _ElementLifecycle.defunct);
if (_lifecycleState != _ElementLifecycle.active) {
return;
}
assert(owner != null);
assert(_lifecycleState == _ElementLifecycle.active);
assert(() {
if (owner!._debugBuilding) {
assert(owner!._debugCurrentBuildTarget != null);
assert(owner!._debugStateLocked);
if (_debugIsInScope(owner!._debugCurrentBuildTarget!)) {
return true;
}
final List<DiagnosticsNode> information = <DiagnosticsNode>[
ErrorSummary('setState() or markNeedsBuild() called during build.'),
ErrorDescription(
),
describeElement('The widget on which setState() or markNeedsBuild() was called was'),
];
return true;
}());
if (dirty) { // 这里进行了返回!
return;
}
_dirty = true;
owner!.scheduleBuildFor(this); // 调用scheduleBuildFor方法, 传入当前Element对象
}
void scheduleBuildFor(Element element) {
assert(element.owner == this);
assert(() {
if (debugPrintScheduleBuildForStacks) {
debugPrintStack(label: 'scheduleBuildFor() called for $element${_dirtyElements.contains(element) ? " (ALREADY IN LIST)" : ""}');
}
return true;
}());
if (element._inDirtyList) {
_dirtyElementsNeedsResorting = true;
return;
}
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled!();
}
_dirtyElements.add(element); //添加到_dirtyElements集合中
element._inDirtyList = true;
return true;
}());
}
从现象推断,整个流程必然会经过setState()-···················->当前State的build()-················->页面绘制-············->屏幕刷新
问题: setState ()是如何更新UI的?
setState源码分析总结:
我们常说的 setState ,其实是调用了 markNeedsBuild ,markNeedsBuild 内部会标记 element 为 diry,添加到BuildOwner对象的_dirtyElements集合中, 然后调用scheduleFrame来注册Vsync回调。 当下一次vsync信号的到来时会执行handleBeginFrame()和handleDrawFrame()来更新UI。
然后在下一帧 WidgetsBinding.drawFrame 才会被绘制,这可以也看出 setState 并不是立即生效的
State#setState 的核心作用就是把持有的元素标脏并申请新帧调度。而只有新帧到来,执行完构建之后,元素的 dirty 才会置为 false 。也就是说,两帧之间,无论调用多少次 setState ,都只会触发一次, 元素标脏 和 申请新帧调度 。这就是为什么连续触发 1000000 次,并无大事发生的原因
setState()会重建, 但是有dirty的判读, 不会经常重建!
dirty state的含义是脏的State
它实际是通过一个Element的东西(我们还没有讲到Flutter绘制原理)的属性来标记的;
将它标记为dirty会等待下一次的重绘检查,强制调用build方法来构建我们的Widget
setState可以分为两个部分:
将element标脏
渲染时将所有脏element都rebuild,且将自己的child进行update
重要的方法: performRebuild()
updateChild(_child, built, slot)
问题: setState每次都会去执行build ()?
父widget
父widget2
子widget3: 用state变量
点击: 调用setstate方法, 然后会创建, 调用 build(). 是不是只重绘制子widget3, 其他的不会重新绘制
setState 触发了对你当前所在的小组件的重建。如果你的整个应用程序只包含一个widget,那么整个widget将被重建,这将使你的应用程序变得缓慢
要把setstate多封装一层, 让setstate咋你自己的widget, 这样就不会重建整个widget
问题: 为什么高位置的setState ()会消耗性能?
虽然setState的调用并没有像 Widget 层那样,在渲染控制层的 Element 那一层重新构建全部element。但是,这并不代表 setState 的使用没问题,首先,像之前篇章说的那样,它会重新构建整个 Widget 树,这会带来性能损耗;其次,由于整个 Widget 树改变了,意味着整棵树对应的渲染层Element对象都会执行 update方法,虽然不一定会重新渲染,但是这整棵树的遍历的性能开销也很高
总结: 虽然每次不会都创建element, 但是遍历有损耗, 提升办法: 只更新需要更新的widget!
当我们在一个高节点调用setState()的时候会构建再次build所有的Widget,虽然不一定挂载到Element树中,但是平时我们使用的Widget中往往嵌套多个其他类型的Widget,每个build()方法走下来最终也会带来不小的开销,因此通过各种状态管理方案,Stream等方式,只做局部刷新,是我们日常开发中应该养成的良好习惯。
流转图:
setState() 添加到_dirtyElements集合中, owner.scheduleBuildFor(this)
rebuild()
StatefulElement-->performRebuild(): 最重要的方法
StatefulElement--->updateChild()
Element---> update();
build()过程虽然只是调用一个组件的构造方法,不涉及对Element树的挂载操作。
但因为我们一个组件往往是N多个Widget的嵌套组合,每个都遍历一遍开销算下来并不小
问题: setState()如何做优化?
要在子控件中调用setState(),
局部刷新 , 但是局部刷新,也只是 setState
的封装
3. 官方 provider: 复杂情况的状态管理
**数据传递: 多个页面之间的传递, 数据变化通知! **
Flutter 官方的状态管理框架 Provider 则相对简单得多
Provider 是一个用来提供数据的框架。它是 InheritedWidget 的语法糖,提供了依赖注入的功能,允许在 Widget 树中更加灵活地处理和传递数据
Provider 就是针对 InheritedWidget
的一个包装工具
在使用Provider的时候,我们主要关心三个概念:
- ChangeNotifier:真正数据(状态)存放的地方
- ChangeNotifierProvider:Widget树中提供数据(状态)的地方,会在其中创建对应的ChangeNotifier
- Consumer:Widget树中需要使用数据(状态)的地方 读Provider
读和写的Provider
ChangeNotifierProvider:Widget树中提供数据(状态)的地方,会在其中创建对应的ChangeNotifier
但是,滥用 Provider.of 方法也有副作用,那就是当数据更新时,页面中其他的子 Widget 也会跟着一起刷新。
可以看到,TestIcon 控件本来是一个不需要刷新的 StatelessWidget,但却因为其父 Widget FloatingActionButton 所依赖的数据资源 counter 发生了变化,导致它也要跟着刷新。
Consumer: 消费, 把数据消费! (用的比 Provider.of 多)
因为Consumer在刷新整个Widget树时,会尽可能少的rebuild Widget。
在数据资源发生变化时,只刷新对资源存在依赖关系的 Widget,而其他 Widget 保持不变呢?
有一个child().不重新构建
Consumer是否是最好的选择呢?并不是,它也会存在弊端 Selector的选择 (使用的比较多)
比如当点击了floatingActionButton时,我们在代码的两处分别打印它们的builder是否会重新调用;
我们会发现只要点击了floatingActionButton,两个位置都会被重新builder;
但是floatingActionButton的位置有重新build的必要吗?没有,因为它是否在操作数据,并没有展示;
如何可以做到让它不要重新build了?使用Selector来代替Consumer
所以在某些情况下,我们可以使用Selector来代替Consumer,性能会更高
Selector和Consumer对比,不同之处主要是三个关键点:
关键点1:泛型参数是两个
泛型参数一:我们这次要使用的Provider
泛型参数二:转换之后的数据类型,比如我这里转换之后依然是使用CounterProvider,那么他们两个就是一样的类型
关键点2:selector回调函数
转换的回调函数,你希望如何进行转换
S Function(BuildContext, A) selector
我这里没有进行转换,所以直接将A实例返回即可
关键点3:是否希望重新rebuild
这里也是一个回调函数,我们可以拿到转换前后的两个实例;
bool Function(T previous, T next);
因为这里我不希望它重新rebuild,无论数据如何变化,所以这里我直接return false;
多状态的资源封装
Provider 的另一个升级版 MultiProvider,来实现多个 Provider 的组合注入
ScrollAwareImageProvider:
ChangeNotifier:
搞清楚TextField是怎么使用ChangeNotifier的了,为什么每次改变TextEditingController的text值,然后在TextField数据框里的数据也及时改变了,其实最后还是用到setState。
原理:
performRebuild() :该回调会在setState或者build的时候会触发;此处做了一个判断,只会在第一次build的时候触发 build()
notifyDependent()
问题: InheritedElement? 而不是用普通的Element?
如Flutter SDK中正是通过InheritedWidget来共享应用主题(Theme)和Locale (当前语言环境)信息
InheritedWidget: 作为根节点, 然后其他子节点, 就可以获取根节点的状态! 但是如果要更新的话, 还是的手动用setstate
最后,我们重写了 updateShouldNotify 方法,这个方法会在 Flutter 判断 InheritedWidget 是否需要重建,从而通知下层观察者组件更新数据时被调用到。在这里,我们直接判断 count 是否相等即可。
定义一个共享数据的InheritedWidget,需要继承自InheritedWidget
- 这里定义了一个of方法,该方法通过context开始去查找祖先的HYDataWidget(可以查看源码查找过程)
- updateShouldNotify方法是对比新旧HYDataWidget,是否需要对更新相关依赖的Widget
InheritedWidget中的属性在子Widget中只能读,如果有修改的场景,我们需要把它和StatefulWidget中的State配套使用。
InheritedWidget是Flutter中的一个功能型Widget,适用于在Widget树中共享数据的场景。通过它,我们可以高效地将数据在Widget树中进行跨层传递
InheritedWidget使用方法
可以看到InheritedWidget的使用方法还是比较简单的,无论Counter在CountContainer下层什么位置,都能获取到其父Widget的计数属性count,再也不用手动传递属性了。
不过,InheritedWidget仅提供了数据读的能力,如果我们想要修改它的数据,则需要把它和StatefulWidget中的State配套使用
使用场景:
InheritedWidget的数据流动方式是从父Widget到子Widget逐层传递
InheritedWidget共有两个方法
1).createElement() (创建对应的Element)
2).updateShouldNotify(covariant InheritedWidget oldWidget)
问题: Flutter中的InheritedWidget的实现原理是怎么样的?
InheritedWidget的原理:
主要是观察模式的思想
源码分析: (重点)
class CountContainer extends InheritedWidget {
// 方便其子 Widget 在 Widget 树中找到它
static CountContainer of(BuildContext context) => context.inheritFromWidgetOfExactType(CountContainer) as CountContainer;
final int count;
CountContainer({
Key key,
@required this.count,
@required Widget child,
}): super(key: key, child: child);
// 判断是否需要更新
@override
bool updateShouldNotify(CountContainer oldWidget) => count != oldWidget.count;
}
Provider 特点:
因为 Provider 实际上是 InheritedWidget 的语法糖,所以通过 Provider 传递的数据从数据流动方向来看,是由父到子(或者反过来)。这时我们就明白了,原来需要把资源放到 FirstPage 和 SecondPage 的父 Widget,也就是应用程序的实例 MyApp 中(当然,把资源放到更高的层级也是可以的,比如放到 main 函数中)
他最主要的功能:就是会调用Element的performRebuild()方法,然后触发ComponentElement的build()方法,最终触发_InheritedProviderScopeElement的build方法
其他: markNeedsNotifyDependents, 我们使用 notifyListeners(),就会触发,这个回调
provide不是调用的setstate()进行状态管理的么?
那怎么会触发到performRebuild()这个方法了?
当我们执行 ChangeNotifer 的 notifyListeners 时,就会最终触发 setState 更新。
执行流程。
ChangeNotifer------>notifyListeners
setState()
InheritedElement------>performRebuild
InheritedElement------>build
InheritedElement------>notifyListeners
我们要知道一个前提:刷新Widget会先进入Element的rebuild方法。然后是performRebuild方法,这个方法Element没做什么,交由具体子类去实现
provider代码原理总结:
wiget是InheritedWidget
1)、 Provider 的内部 DelegateWidget 是一个 StatefulWidget ,所以可以更新且具有生命周期。
2)、状态共享是使用了 InheritedProvider 这个 InheritedWidget 实现的。
4.手写简单版provider
使用
view
class CounterEasyPPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierEasyP(
create: (BuildContext context) => CounterEasyP(),
builder: (context) => _buildPage(context),
);
}
Widget _buildPage(BuildContext context) {
final easyP = EasyP.of<CounterEasyP>(context);
return Scaffold(
appBar: AppBar(title: Text('自定义状态管理框架-EasyP范例')),
body: Center(
child: EasyPBuilder<CounterEasyP>(() {
return Text(
'点击了 ${easyP.count} 次',
style: TextStyle(fontSize: 30.0),
);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () => easyP.increment(),
child: Icon(Icons.add),
),
);
ChangeNotifier
class CounterEasyP extends ChangeNotifier {
int count = 0;
void increment() {
count++;
notifyListeners();
}
}
核心类: 3个
ChangeNotifierProvider: 最重要的方法
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatelessWidget {
ChangeNotifierEasyP({
Key? key,
required this.create,
this.builder,
this.child,
}) : super(key: key);
final T Function(BuildContext context) create;
final Widget Function(BuildContext context)? builder;
final Widget? child;
@override
Widget build(BuildContext context) { // 重写 build()方法, 返回InheritedWidget对象
assert(
builder != null || child != null,
'$runtimeType must specify a child',
);
return EasyPInherited(
create: create,
child: builder != null
? Builder(builder: (context) => builder!(context))
: child!,
);
}
}
class EasyPInherited<T extends ChangeNotifier> extends InheritedWidget { // 实现一个InheritedWidget
EasyPInherited({
Key? key,
required Widget child,
required this.create,
}) : super(key: key, child: child);
final T Function(BuildContext context) create;
@override
bool updateShouldNotify(InheritedWidget oldWidget) => false; // 重写updateShouldNotify方法
@override
InheritedElement createElement() => EasyPInheritedElement(this);
}
class EasyPInheritedElement<T extends ChangeNotifier> extends InheritedElement { // InheritedElement重写
EasyPInheritedElement(EasyPInherited<T> widget) : super(widget);
bool _firstBuild = true;
bool _shouldNotify = false;
late T _value;
late void Function() _callBack;
T get value => _value;
@override
void performRebuild() { // 实现performRebuild()
if (_firstBuild) {
_firstBuild = false;
_value = (widget as EasyPInherited<T>).create(this);
_value.addListener(_callBack = () {
// 处理刷新逻辑,此处无法直接调用notifyClients
// 会导致owner!._debugCurrentBuildTarget为null,触发断言条件,无法向后执行
_shouldNotify = true;
markNeedsBuild();
});
}
super.performRebuild();
}
@override
Widget build() { // build()重写
if (_shouldNotify) {
_shouldNotify = false;
notifyClients(widget as EasyPInherited<T>);
}
return super.build();
}
@override
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
//此处就直接刷新添加的监听子Element了,不各种super了
dependent.markNeedsBuild();
// super.notifyDependent(oldWidget, dependent);
}
@override
void unmount() {
_value.removeListener(_callBack);
_value.dispose();
super.unmount();
}
}
Provider
class Provider { // 不包含核心逻辑,仅仅是封装
/// 获取EasyP实例
/// 获取实例的时候,listener参数老是写错,这边直接用俩个方法区分了
static T of<T extends ChangeNotifier>(BuildContext context) {
return _getInheritedElement<T>(context).value;
}
/// 注册监听控件
static T register<T extends ChangeNotifier>(BuildContext context) {
var element = _getInheritedElement<T>(context);
context.dependOnInheritedElement(element);
return element.value;
}
/// 获取距离当前Element最近继承InheritedElement<T>的组件
//调用dependOnInheritedWidgetOfExactType() 和 getElementForInheritedWidgetOfExactType()的区别就是前者会注册依赖关系,而后者不会
static EasyPInheritedElement<T>
_getInheritedElement<T extends ChangeNotifier>(BuildContext context) {
var inheritedElement = context
.getElementForInheritedWidgetOfExactType<EasyPInherited<T>>()
as EasyPInheritedElement<T>?;
if (inheritedElement == null) {
throw EasyPNotFoundException(T);
}
return inheritedElement;
}
}
class EasyPNotFoundException implements Exception {
EasyPNotFoundException(this.valueType);
final Type valueType;
@override
String toString() => 'Error: Could not find the EasyP<$valueType>';
}
builder :
class EasyPBuilder<T extends ChangeNotifier> extends StatelessWidget {
const EasyPBuilder(
this.builder, {
Key? key,
}) : super(key: key);
final Widget Function() builder;
@override
Widget build(BuildContext context) {
EasyP.register<T>(context);
return builder();
}
}
案例:
自己写一个封装的controller()
3个核心类:
build:
Provider:
ChangeNotifierProvider:
点击事件: 数据变化调用provide里面的变化方法,
ui更新, 也是拿provider的值
Widget _buildPage(BuildContext context) {
final easyP = EasyP.of<CounterEasyP>(context);
return Scaffold(
appBar: AppBar(title: Text('自定义状态管理框架-EasyP范例')),
body: Center(
child: EasyPBuilder<CounterEasyP>(() {
return Text(
'点击了 ${easyP.count} 次',
style: TextStyle(fontSize: 30.0),
);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () => easyP.increment(),
child: Icon(Icons.add),
),
);
}
}
创建了一个widget:
class CounterEasyPPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierEasyP(
create: (BuildContext context) => CounterEasyP(),
builder: (context) => _buildPage(context),
);
}
把需要更新的widget传入到构造方法中去了! ChangeNotifier传入, buildcontext也在
5. 简单的状态管理:
对于数据的跨层传递,Flutter还提供了三种方案:InheritedWidget、Notification和EventBus。接下来,我将依次为你讲解这三种方案。
b2a78dbefdf30895504b2017355ae066.png
5.1 InheritedWidget 原理如上
5.2 Notification
数据流动方式是从子Widget向上传递至父Widget。这样的数据传递机制适用于子Widget状态变更,发送通知上报的场景
Notification
是一种用于在小部件树中传递信息的机制,它可以用于实现子树中的特定部分之间的通信。Notification
并不像状态管理或全局状态传递那样普遍,它主要用于特定场景下的通信,比如当某个事件发生时,需要在小部件树的各个部分之间传递消息。Notification
的工作方式是通过Notification
对象在小部件树中传递,然后从父级小部件开始逐级向上冒泡,直到找到一个处理该通知的小部件为止。每个处理通知的小部件可以根据需要执行特定的操作。你可以把InheritedWidget 理解为从上到下传递、共享的方式,而Notification
则是从下往上。Notification
它提供了dispatch
方法,沿着context对应的Element节点向上逐层发送通知。
跨组件事件传递
5.3 EventBus
在组件之间如果有事件需要传递,一方面可以一层层来传递,另一方面我们也可以使用一个EventBus工具来完成。
其实EventBus在Vue、React中都是一种非常常见的跨组件通信的方式:
EventBus相当于是一种订阅者模式,通过一个全局的对象来管理;
这个EventBus我们可以自己实现,也可以使用第三方的EventBus;
无需发布者与订阅者之间存在父子关系的数据同步机制。
无论是InheritedWidget还是Notificaiton,它们的使用场景都需要依靠Widget树,也就意味着只能在有父子关系的Widget之间进行数据共享。但是,组件间数据传递还有一种常见场景:这些组件间不存在父子关系。这时,事件总线EventBus就登场了。
事件总线是在Flutter中实现跨组件通信的机制。它遵循发布/订阅模式,允许订阅者订阅事件,当发布者触发事件时,订阅者和发布者之间可以通过事件进行交互。发布者和订阅者之间无需有父子关系,甚至非Widget对象也可以发布/订阅。这些特点与其他平台的事件总线机制是类似的。
总结:
这里我准备了一张表格,把属性传值、InheritedWidget、Notification与EventBus这四种数据共享方式的特点和使用场景做了简单总结,供你参考:
6. 下面是对Provider、BLoC、Redux、GetX、Riverpod和MobX等Flutter状态管理库的一些对比:
状态管理比较.jpg`1). InheritedWidget:在 Flutter 中,所有 Widget 都是通过父 Widget 构建出来的,父 Widget 可以通过 InheritedWidget 共享状态给子 Widget,子 Widget 可以通过调用 InheritedWidget.of() 方法来获取共享的状态。
2). Provider:
- 适用场景: 适用于中小型应用,需要在多个层级共享状态的场景。
- 特点: 轻量级,使用InheritedWidget来共享状态,支持各种类型的状态,易于上手。
- 优势: 简单易用,不需要大量的额外代码,具有高性能,适用于简单的状态共享。
- 劣势: 在大型应用中可能难以管理复杂的状态。
状态管理混乱,虽然用了 provider
来做状态管理,但一些代码如:异步请求、事件响应等还是会掺杂在UI页面的代码里面,一旦页面的各种 Widget
多了起来之后,显得非常严重,而且对业务逻辑的测试也不方便,多个组件可能需要共享相同的数据或状态,需要在每个组件中分别创建 Provider
实例,容易导致代码冗余,如果只需要更新页面的部分 Widget
使用Provider
还会导致嵌套过深,也可能导致性能问题、状态不一致以及难以追踪的错误
3). ScopedModel:ScopedModel 也可以实现状态共享,但它的思想是将数据放在一个共享的 Model 中,然后让需要用到这些数据的 Widget 注册监听该 Model,当 Model 的数据改变时,通知监听它的 Widget 更新。
4). Stream
Stream是一种用于在应用程序中管理状态和数据流的重要工具。Stream是异步数据流的抽象表示,它可以在应用程序中传递和监听数据的变化。但是它和Flutter关系并不大,它是通过纯dart去实现的。你可以理解为flutter只是通过StreamBuilder
去构建了一个Stream
通道。它的使用其实也并没有复杂太多,通常只需要创建StreamController
,然后去监听控制器(可以直接去监听StreamController
,然后通过setState更新UI,也可以通过StreamBuilder
),最后将更新后的数据通过Stream的sink属性添加到Stream中即可。知名的状态管理库Bloc,就是基于Stream的封装。
5). BLoC:
BLoC 算是 Flutter 早期比较知名的状态管理框架, 它是基于事件驱动来实现的状态管理
BLoC 是业务逻辑组件的缩写,它使用 Streams 和Provider 库将业务逻辑和 UI 分离开来,可以用来管理状态和处理用户输入。作者在这里使用了Bloc用于状态管理
基于 Stream
的封装可以更方便做一些事件状态的监听和转换
- 适用场景: 适用于复杂的应用,需要分离业务逻辑和UI的场景。
- 特点: 通过Streams管理状态和业务逻辑,将界面层与业务逻辑层分开,适合中大型应用。
- 优势: 适合处理复杂的状态变化和异步操作,便于测试和维护。
- 劣势: 在简单应用中可能显得过于复杂。需要写更多的代码,开发节奏会有点影响
6). Redux:
- Redux 是一种状态管理模式,它将状态和状态更新封装在一个可预测的单向数据流中,可以用于处理应用程序的复杂状态。
前端开始者对 redux 可能会更熟悉一些
在 flutter_redux 中,开发者的每个操作都只是一个 Action
,而这个行为所触发的逻辑完全由 middleware
和 reducer
决定
- 适用场景: 适用于需要管理大量复杂状态的应用。
- 特点: 基于单一状态源和不可变状态,通过Actions和Reducers来管理状态变化。
- 优势: 严格的状态管理,适用于大型应用,具有强大的开发工具和中间件。
- 劣势: 在小型应用中可能过于繁琐,学习曲线较陡`
7). ;不在维护了2012年后没有维护了! 第三方框架, 咸鱼Fish Redux的框架
8). Riverpod:
适用场景: 适用于需要更强大、更简单的状态管理和依赖注入的场景。
特点: 基于Provider的升级版本,提供更简单、更强大的API,支持多种状态管理模式。
优势: 代码清晰,性能高效,支持多种状态管理模式,适用于各种规模的项目。
劣势: 相对较新的库,社区可能还在成长。
随着 Flutter 的发展,这些年 Flutter 上的状态管理框架如“雨后春笋”般层出不穷,而近一年以来最受官方推荐的状态管理框架无疑就是 Riverpod
直观的就是 Riverpod 中的 Provider 可以随意写成全局, 如果说 Riverpod 最明显的特点是什么,那就是外部不依赖 BuildContext
它的作者也是 Provider 的作者,同时也是 Flutter 官方推荐的状态管理库
如何实现不依赖 BuildContext?
在 Riverpod 里基本是每一个 “Provider” 都会有一个自己的 “Element” ,然后通过 WidgetRef 去 Hook 后成为 BuildContext 的替代,所以这就是 Riverpod 不依赖 Context 的 “魔法” 之一
9). GetX:
适用场景: 适用于快速开发和中小型应用,需要轻量级状态管理和依赖注入的场景。
特点: 简单易用,提供状态管理、依赖注入和路由导航的综合解决方案。
优势: 低学习曲线,高性能,适用于快速迭代的小型项目。
劣势: 对于大型复杂应用,可能需要更复杂的状态管理方案。
10). MobX:
适用场景: 适用于需要响应式编程和可观察对象的场景。
特点: 通过可观察对象和反应式编程来管理状态,支持多种数据变化方式。提高开发效率
优势: 简化了状态管理,具有响应式编程的特点,易于学习和使用。
劣势: 相对较新的库,可能在一些大型项目中缺乏一些高级功能。全家桶,做的太多对于一些使用者来说是致命缺点,需要解决的 Bug 也多
11). rxdart: 太老了
网友评论