为了弄懂 Flutter 的状态管理,- 我用 10 种方法改造了 counter-app(1)
InheritedWidget
InheritedWidget的主要作用是在 Widget 树中有效地传递信息.
如果没有InheritedWidget
, 我们想把一个数据从 widget 树的上层传到某一个 child widget, 要利用途中的每一个构造函数, 一路传递下来.
Flutter 中常用的Theme
,?Style
,?MediaQuery
等就是 inherited widget, 所以在程序里的各种地方都可以访问到它们.
InheritedWidget
也会用在其他状态管理模式中, 作为传递数据的方法.
InheritedWidget 状态管理实现
当用InheritedWidget
做状态管理时, 基本思想就是把状态提上去.当子 widgets 之间需要共享状态, 那么就把状态保存在它们共有的 parent 中.
首先定义一个InheritedWidget
的子类, 包含状态数据.覆写两个方法:
提供一个静态方法给 child 用于获取自己. (命名惯例
of(BuildContext)
).判断是否发生了数据更新.
class CounterStateContainer extends InheritedWidget {final CounterModel data;
CounterStateContainer({Key key,@required Widget child,@required this.data,}) : super(key: key, child: child);
@overridebool updateShouldNotify(CounterStateContainer oldWidget) {return data.counter.value != oldWidget.data.counter.value;}
static CounterModel of(BuildContext context) {return context.dependOnInheritedWidgetOfExactType<CounterStateContainer>().data;}}
之后用这个CounterStateContainer
放在上层, 包含了数据和所有状态相关的 widgets.child widget 不论在哪一层都可以方便地获取到状态数据.
Text('${CounterStateContainer.of(context).counter.value}',),
代码分支:?inherited-widget.
InheritedWidget 缺点
InheritedWidget
解决了访问状态和根据状态更新的问题, 但是改变 state 却不太行.
accessing state
updating on change
mutating state -> X
首先, 不支持跨页面(route)的状态, 因为 widget 树变了, 所以需要进行跨页面的数据传递.
其次,?InheritedWidget
它包含的数据是不可变的, 如果想让它跟踪变化的数据:
把它包在一个
StatefulWidget
里.在
InheritedWidget
中使用ValueNotifier
,?ChangeNotifier
或 steams.
这个方案也是了解一下, 实际的全局状态管理还是用更成熟的方案.但是它的原理会被用到其他方案中作为对象传递的方式.
Scoped Model
scoped model 是一个外部 package: https://pub.dev/packages/scoped_modelScoped Model 是基于InheritedWidget
的. 思想仍然是把状态提到上层去, 并且封装了状态改变的通知部分.
Scoped Model 实现
它官方提供例子就是改造 counter: https://pub.dev/packages/scoped_model#-example-tab-
添加 scoped_model 依赖.
创建数据类, 继承
Model
.
import 'package:scoped_model/scoped_model.dart';
class CounterModel extends Model {int _counter = 0;
int get counter => _counter;
void increment() {_counter++;notifyListeners();}
void decrement() {_counter--;notifyListeners();}}
其中数据变化的部分会通知 listeners, 它们收到通知后会 rebuild.
在上层初始化并提供数据类, 用ScopeModel
.
访问数据有两种方法:
用
ScopedModelDescendant
包裹 widget.用
ScopedModel.of
静态方法.
使用的时候注意要提供泛型类型, 会帮助我们找到离得最近的上层ScopedModel
.
ScopedModelDescendant<CounterModel>(builder: (context, child, model) {return Text(model.counter.toString(),);}),
数据改变后, 只有ScopedModelDescendant
会收到通知, 从而 rebuild.
ScopedModelDescendant
有一个rebuildOnChange
属性, 这个值默认是 true.对于 button 来说, 它只是控制改变, 自身并不需要重绘, 可以把这个属性置为 false.
ScopedModelDescendant<CounterModel>(rebuildOnChange: false,builder: (context, child, model) {return FloatingActionButton(onPressed: model.increment,tooltip: 'Increment',child: Icon(Icons.add),);},),
scoped model 这个库帮我们解决了数据访问和通知的问题, 但是 rebuild 范围需要自己控制.
access state
notify other widgets
minimal rebuild -> X -> 因为需要开发者自己来决定哪一部分是否需要被重建, 容易被忘记.
代码分支:?scoped-model
Provider
Provider 是官方文档的例子用的方法.去年的 Google I/O 2019 也推荐了这个方法.和 BLoC 的流式思想相比, Provider 是一个观察者模式, 状态改变时要notifyListeners()
.
有一个 counter 版本的 sample: https://github.com/flutter/samples/tree/master/provider_counter
Provider 的实现在内部还是利用了InheritedWidget
.Provider 的好处: dispose 指定后会自动被调用, 支持MultiProvider
.
Provider 实现
model 类继承
ChangeNotifer
, 也可以用with
.
class CounterModel extends ChangeNotifier {int value = 0;
void increment() {value++;notifyListeners();}
void decrement() {value--;notifyListeners();}}
数据提供者:?
ChangeNotifierProvider
.
void main() => runApp(ChangeNotifierProvider(create: (context) => CounterModel(),child: MyApp(),));
数据消费者/操纵者, 有两种方式:?
Consumer
包裹, 用Provider.of
.
Consumer<CounterModel>(builder: (context, counter, child) => Text('${counter.value}',),),
FAB:
FloatingActionButton(onPressed: () =>Provider.of<CounterModel>(context, listen: false).increment(),),
这里listen
置为 false 表明状态变化时并不需要 rebuild FAB widget.
Provider 性能相关的实现细节
Consumer
包裹的范围要尽量小.listen 变量.
child 的处理.?
Consumer
中 builder 方法的第三个参数.
可以用于缓存一些并不需要重建的 widget:
return Consumer<CartModel>(builder: (context, cart, child) => Stack(children: [// Use SomeExpensiveWidget here, without rebuilding every time.child,Text("Total price: ${cart.totalPrice}"),],),// Build the expensive widget here.child: SomeExpensiveWidget(),);
代码分支:?provider.
BLoC
BLoC 模式的全称是: business logic component.
所有的交互都是 a stream of asynchronous events.Widgets + Streams = Reactive
.
BLoC 的实现的主要思路: Events in -> BloC -> State out.
Google I/O 2018 上推荐的还是这个, 2019 就推荐 Provider 了.当然也不是说这个模式不好, 架构模式本来也没有对错之分, 只是技术选型不同.
BLoC 手动实现
不添加任何依赖可以手动实现 BLoC, 利用:
Dart SDK > dart:async >?
Stream
.Flutter 的
StreamBuilder
: 输入是一个 stream, 有一个 builder 方法, 每次 stream 中有新值, 就会 rebuild.
可以有多个 stream, UI 只在自己感兴趣的信息发生变化的时候重建.
BLoC 中:
输入事件:?
Sink<Event> input
.输出数据:?
Stream<Data> output
.
CounterBloc 类:
class CounterBloc {int _counter = 0;
final _counterStateController = StreamController<int>();
StreamSink<int> get _inCounter => _counterStateController.sink;
Stream<int> get counter => _counterStateController.stream;
final _counterEventController = StreamController<CounterEvent>();
Sink<CounterEvent> get counterEventSink => _counterEventController.sink;
CounterBloc() {_counterEventController.stream.listen(_mapEventToState);}
void _mapEventToState(CounterEvent event) {if (event is Incre
mentEvent) {_counter++;} else if (event is DecrementEvent) {_counter--;}_inCounter.add(_counter);}
void dispose() {_counterStateController.close();_counterEventController.close();}}
有两个StreamController
, 一个控制 state, 一个控制 event.
读取状态值要用StreamBuilder
:
StreamBuilder(stream: _bloc.counter,initialData: 0,builder: (BuildContext context, AsyncSnapshot<int> snapshot) {return Text('${snapshot.data}',);},)
而改变状态是发送事件:
FloatingActionButton(onPressed: () => _bloc.counterEventSink.add(IncrementEvent()),),
实现细节:
每个屏幕有自己的 BLoC.
每个 BLoC 必须有自己的
dispose()
方法. -> BLoC 必须和StatefulWidget
一起使用, 利用其生命周期释放.
代码分支:?bloc
BLoC 传递: 用 InheritedWidget
手动实现的 BLoC 模式, 可以结合InheritedWidget
, 写一个 Provider, 用来做 BLoC 的传递.
代码分支:?bloc-with-provider
BLoC rxdart 实现
用了 rxdart package 之后, bloc 模块的实现可以这样写:
class CounterBloc {int _counter = 0;
final _counterSubject = BehaviorSubject<int>();
Stream<int> get counter => _counterSubject.stream;
final _counterEventController = StreamController<CounterEvent>();
Sink<CounterEvent> get counterEventSink => _counterEventController.sink;
CounterBloc() {_counterEventController.stream.listen(_mapEventToState);}
void _mapEventToState(CounterEvent event) {if (event is IncrementEvent) {_counter++;} else if (event is DecrementEvent) {_counter--;}_counterSubject.add(_counter);}
void dispose() {_counterSubject.close();_counterEventController.close();}}
BehaviorSubject
也是一种StreamController
, 它会记住自己最新的值, 每次注册监听, 会立即给你最新的值.
代码分支:?bloc-rxdart.
BLoC Library
可以用这个 package 来帮我们简化代码: https://pub.dev/packages/flutter_bloc
自己只需要定义 Event 和 State 的类型并传入, 再写一个逻辑转化的方法:
class CounterBloc extends Bloc<CounterEvent, CounterState> {@overrideCounterState get initialState => CounterState.initial();
@overrideStream<CounterState> mapEventToState(CounterEvent event) async* {if (event is IncrementEvent) {yield CounterState(counter: state.counter + 1);} else if (event is DecrementEvent) {yield CounterState(counter: state.counter - 1);}}}
用BlocProvider
来做 bloc 的传递, 从而不用在构造函数中一传到底.
访问的时候用BlocBuilder
或BlocProvider.of<CounterBloc>(context)
.
BlocBuilder(bloc: BlocProvider.of<CounterBloc>(context),builder: (BuildContext context, CounterState state) {return Text('${state.counter}',);},),
这里 bloc 参数如果没有指定, 会自动向上寻找.
BlocBuilder
有一个参数condition
, 是一个返回 bool 的函数, 用来精细控制是否需要 rebuild.
FloatingActionButton(onPressed: () =>BlocProvider.of<CounterBloc>(context).add(IncrementEvent()),),
代码分支:?bloc-library.
rxdart
这是个原始版本的流式处理.
和 BLoC 相比, 没有专门的逻辑模块, 只是改变了数据的形式.
利用 rxdart, 把数据做成流:
class CounterModel {BehaviorSubject _counter = BehaviorSubject.seeded(0);
get stream$ => _counter.stream;
int get current => _counter.value;
increment() {_counter.add(current + 1);}
decrement() {_counter.add(current - 1);}}
获取数据用StreamBuilder
, 包围的范围尽量小.
StreamBuilder(stream: counterModel.stream,builder: (BuildContext context, AsyncSnapshot snapshot) {return Text('{snapshot.data}',);},),
Widget dispose 的时候会自动解绑.
数据传递的部分还需要进一步处理.
代码分支:?rxdart.
Redux
Redux 是前端流行的, 一种单向数据流架构.
概念:
Store
: 用于存储State
对象, 代表整个应用的状态.Action
: 事件操作.Reducer
: 用于处理和分发事件的方法, 根据收到的Action
, 用一个新的State
来更新Store
.View
: 每次 Store 接到新的 State,?View
就会重建.
Reducer
是唯一的逻辑处理部分, 它的输入是当前State
和Action
, 输出是一个新的State
.
Flutter Redux 状态管理实现
首先定义好 action, state:
enum Actions {Increment,Decrement,}
class CounterState {int _counter;
int get counter => _counter;
CounterState(this._counter);}
reducer 方法根据 action 和当前 state 产生新的 state:
CounterState reducer(CounterState prev, dynamic action) {if (action == Actions.Increment) {return new CounterState(prev.counter + 1);} else if (action == Actions.Decrement) {return new CounterState(prev.counter - 1);} else {return prev;
评论