在刚刚接触flutter的时候使用过fish_redux这个状态管理库,当时觉得好复杂,文件多还跳来跳去的,如今多年过去了,公司用到了flutter_redux这个状态管理库,于是对redux有了新的认识
先说下flutter_redux的整体原理:
1.StoreProvider继承自InheritedWidget,主要作用是为了子控件获取Store的.与Provider状态管理框架不同的是,这里的Store并不是我们的状态,而Provider的data是状态,UI的更新依赖于InheritedWidget的,这里StoreProvider只是为了将Store在子树中传递,不涉及Store的修改以及UI的刷新
2.StoreConnector这个组件通过继承体系发现最终返回的是一个StreamBuilder,所以真正起到响应式状态更新的是这个StreamBuilder
所以总结: flutter_redux用StoreProvider(InheritedWidget)来传递Store,用StoreConnector(StreamBuilder)来响应状态的改变
对于原理其实很好理解,但是为什么fish_redux会受到中大型项目的青睐呢?小项目用起来是费力不讨好的
主要的一点就是分层明确:
对于大的项目,首先要考虑的是好管理,好维护,所以清晰的目录结构以及解耦是大项目选择状态管理框架的重要选项,而redux做到了这一点,他明确分了三大块:
state: 我们的状态,比如int count = 0;
action: 事件,比如点击按钮,我们要让屏幕上数字变化
reducer: 真正处理事件,这里会返回新的(或者是返回旧的)状态,触发StreamBuilder的builder
开发UI的同学不需要关心刷新的事情,需要的时候分发事件就可以了;
而写逻辑的同学也不需要关心哪里调用的我的方法,只针对事件来处理逻辑
那么flutter_redux是如何将这些内容串联起来的呢?
下面就是我学习后的理解
我们需要看下Store的初始化代码
Store(
this.reducer, {
required State initialState,
List<Middleware<State>> middleware = const [],
bool syncStream = false,
bool distinct = false,
}) : _changeController = StreamController.broadcast(sync: syncStream) {
_state = initialState;
_dispatchers = _createDispatchers(
middleware,
_createReduceAndNotify(distinct),
);
}
middleware先忽略,需要传入一个reducer,一个初始状态,reducer
是一个函数类型
List<NextDispatcher> _createDispatchers(
List<Middleware<State>> middleware,
NextDispatcher reduceAndNotify,
) {
final dispatchers = <NextDispatcher>[]..add(reduceAndNotify);
// Convert each [Middleware] into a [NextDispatcher]
for (var nextMiddleware in middleware.reversed) {
final next = dispatchers.last;
dispatchers.add(
(dynamic action) => nextMiddleware(this, action, next),
);
}
return dispatchers.reversed.toList();
}
NextDispatcher _createReduceAndNotify(bool distinct) {
return (dynamic action) {
final state = reducer(_state, action);
if (distinct && state == _state) return;
_state = state;
_changeController.add(state);
};
}
这里可以看到_changeController = StreamController.broadcast(sync: syncStream)
StreamController被创建了,并且是广播类型的,因为可能被多个StreamBuilder监听
然后就是_createDispatchers
这个方法,如果不考虑middleware
,那么这个数组里只会有reduceAndNotify
这一个元素,而reduceAndNotify
就是_createReduceAndNotify(distinct)
,再_createReduceAndNotify
里返回了一个回调函数,所以dispatchers
中存储的就是这个回调函数,在这个函数里边调用了reducer
,然后将reducer
返回的state替换掉之前的,然后就是StreamController
的add
操作,这个回调函数在什么时候执行呢?
就是在store
的dispatch
函数中,在我们的UI中,我们通过调用store
的dispatch
函数去分发事件
dynamic dispatch(dynamic action) {
return _dispatchers[0](action);
}
以上是关于状态如何被多订阅StreamController.add到的,现在就来思考一个问题,如果只是简单的add,那必然会导致所有监听StreamController.stream 的 StreamBuilder 都去刷新,比如我state中有两个参数
class MyState {
final int age;
final String name;
MyState({required this.age, required this.name});
MyState copy({int? age, String? name}) {
return MyState(age: age??this.age, name: name??this.name);
}
}
我在一个地方想要监听age,在另一个地方想要监听name,我想要的结果是我在只修改age的时候,监听name的模块不会被刷新,我在只修改name的时候,age的模块不会被刷新,但是如果只是简单的Listen Store中的_changeController.stream,那结果必然是都要刷新,flutter_redux是如何解决这个问题的呢?这里就引出了StoreConnector
两个属性
//将Store转换为ViewModel。生成的ViewModel将被传递给构建器函数。
final StoreConverter<S, ViewModel> converter;
//作为性能优化,只有当ViewModel发生变化时,Widget才能重新构建。
//为了使其正常工作,您必须为ViewModel实现==和hashCode,并在创建StoreConnector时将
//distinct选项设置为true。
final bool distinct;
通过注解可以看到,这两个属性就是用来解决上面这种刷新风暴的(如果我state中有100个属性,那必然是刷新风暴),这里主要还是在stream上做文章,我们知道stream是可以使用map,where这些方法的,上面我们说到StoreConnector本质就是使用了一个StreamBuilder
StreamBuilder<ViewModel>(
stream: _stream,
builder: (context, snapshot) {
if (_latestError != null) throw _latestError!;
return widget.builder(
context,
_requireLatestValue,
);
},
)
但是这里的_stream是经过处理的,在initState中
@override
void initState() {
widget.onInit?.call(widget.store);
_computeLatestValue();
if (widget.onInitialBuild != null) {
WidgetsBinding.instance?.addPostFrameCallback((_) {
widget.onInitialBuild!(_requireLatestValue);
});
}
_createStream();
super.initState();
}
void _createStream() {
_stream = widget.store.onChange
.where(_ignoreChange) //可以抛给外界判断是否需要这次流数据
.map(_mapConverter) // 将state变成更小的数据 ,比如上面的MyState我只监听age,那么这里就可以回调回来state.age
.where(_whereDistinct)//这里是根据distinct,以及state.age来自动过滤的
.transform(StreamTransformer.fromHandlers(
handleData: _handleChange,
handleError: _handleError,
));
}
bool _ignoreChange(S state) {
if (widget.ignoreChange != null) {
return !widget.ignoreChange!(widget.store.state);
}
return true;
}
ViewModel _mapConverter(S state) {
return widget.converter(widget.store);
}
bool _whereDistinct(ViewModel vm) {
if (widget.distinct) {
return vm != _latestValue;
}
return true;
}
_whereDistinct
的作用就是,当distinct为true时,判断新数据和旧数据是否相同(这里也能很好的理解为什么说必须为ViewModel实现==和hashCode),如果不同返回true(需要刷新),相同返回false(不接收这次的流数据).
这样一来,比如我监听的是age,但是我state中name改变了,数据流经_mapConverter
的时候会将state转变成age,_whereDistinct
会判断新旧age是否相同,这样一来,name的修改,就不会影响这里的刷新了,所以如果你想要name变化的同时这个模块也被刷新,distinct
设置为false就好了,_whereDistinct
直接返回true,直接放行
到这里,如果按照上文中不考虑middleware
,那么_dispatchers中只会有一个回调函数,取出并且执行它,就完成了一系列的状态变化页面刷新,如此一来就完美的串联起了state
,action
,reducer
这几个角色
最后呢说说middleware
,在没有他的时候
![](https://img.haomeiwen.com/i5976114/c5df68185ee3d5a2.png)
我们的
view
直接触发action
,给到reducer
去处理,但是这里就会有些问题,reducer
是同步方法,如果我们需要一个网络请求怎么办,我们不能再reducer
中做一步操作,所以flutter_redux给出了一个方法就是不直接将action
给到reducer
,等网络请求完成后再去将获取到的数据包装成action给到reducer,如图
![](https://img.haomeiwen.com/i5976114/716729ac4b7cd726.png)
middleware
中间件的作用,因为我们不能一步到位,所以迫不得已需要这个他具体怎么做到的,我们需要回头看Store初始化的代码,里边调用了
_createDispatchers
List<NextDispatcher> _createDispatchers(
List<Middleware<State>> middleware,
NextDispatcher reduceAndNotify,
) {
final dispatchers = <NextDispatcher>[]..add(reduceAndNotify);
// Convert each [Middleware] into a [NextDispatcher]
for (var nextMiddleware in middleware.reversed) {
final next = dispatchers.last;
dispatchers.add(
(dynamic action) => nextMiddleware(this, action, next),
);
}
return dispatchers.reversed.toList();
}
我们发现middleware
被倒序遍历,然后添加完后返回的时候又是对dispatchers
做了倒序,所以最后的效果是reduceAndNotify
(也就是reducer处理action)放到最后,前边的middleware
顺序就是初始化时候的顺序,然后每一个middleware
都指向下一个dispatcher
,这样就做到了,中间件都执行一遍,然后给到reducer
处理,而中间件是可以async来修饰执行的
比如常用的一个
typedef ThunkAction<State> = dynamic Function(Store<State> store);
dynamic thunkMiddleware<State>(
Store<State> store,
dynamic action,
NextDispatcher next,
) {
if (action is ThunkAction<State>) {
return action(store);
} else {
return next(action);
}
}
//我们可以这样初始化
final store =
Store<CounterState>(counterReducer, initialState: CounterState(0,0),middleware: [
thunkMiddleware
]);
所以按照上面的代码逻辑最终在dispatchers
这个数组里存储了两个回调函数一个是thunkMiddleware
,一个是reduceAndNotify
,并且是thunkMiddleware
在前,这个时候如果调用dispatch
,会先取到thunkMiddleware
如果action是ThunkAction
的类型,thunkMiddleware
会去执行action(因为ThunkAction
是个函数类型,action是个回调函数)
所以我们可以写个ThunkAction
类型的函数
test(Store<CounterState> store) async {
final searchResults = await Future.delayed(
Duration(seconds: 1),
() => CounterAddAction(2),
);
store.dispatch(searchResults);
}
这里边进行了模拟网络请求,然后包装成新的action
,再次调用dispatch
函数,会再次进入thunkMiddleware
,
由于CounterAddAction
不是ThunkAction
类型,所以会走else,由下一个dispatcher处理,而下一个就是reducer了
我们可以看到test
函数是个异步函数,网络请求,延时操作可以在这里进行.
有了middleware
,我们可以方便的对一个事件进行层层处理,甚至可以截停事件
还有一种方式就是有一个事件action1(请求)直接调用dispatch
给到reducer
,reducer
请求网络数据并且将原来的state返回,然后通过then
的方式再次调用dispatch
,而这次的action2(刷新)才是真正刷新UI.
到此有关flutter_redux学习总结完毕,欢迎大家沟通交流!
网友评论