本文发布在使用 redux 管理 flutter 应用数据,后续一直会更新,感兴趣的可以关注一下。
最近在学 flutter,边学边记录了一本 写给前端看的 flutter 笔记,感兴趣的小伙伴可以一起来完善他。
使用 redux 管理 flutter 应用数据
redux 是什么?简单来说,就是为了解决 UI 层状态管理的方案,如果不熟悉,请先看文档学习一下,今天的重点不是学习 redux,而是直接用 redux 管理 flutter 的状态。
和 react 的比较
首先,flutter 和 react 真的太像了,连状态管理都有 redux 方案:
| flutter | react |
| :--- | :-- |
| redux.dart | redux |
| flutter_redux | react-redux |
| redux_thunk | redux-thunk |
另外,甚至还有 redux-epics 能够搭配 rxdart 对标 redux-observable。对于 rxjs 熟悉的人应该也能够很快地开始 rxdart 了,再次证明知识是相似的,技能也是能够越学越快的。
为了不混淆大家,接下来我把 react 中的 redux 称为 react/redux
, flutter 中的 redux 称为 flutter/redux
。
正题
现在我们用 flutter 写一个简单的 redux 例子:
完整代码在 https://github.com/riskers/flutter_notebook_redux ,有过 redux 经验的应该能看明白。使用到了:
- redux
- flutter_redux
- redux_thunk
项目架构完全是之前按照我之前在 React 中的经验来做的
有几点需要注意:
- 初始化 Store
为了方便,可以在 state 中以一个静态方法初始化:
static AppState initialState() {
return AppState(
count: 0,
clickCount: 0,
);
}
然后在入口文件中调用:
final store = Store<AppState>(
reducers,
middleware: [thunkMiddleware],
initialState: AppState.initialState(), // 调用
);
2. reducer 复制对象的方法
在 react/redux 中我们习惯这样写 reducer:
const reducer = (state={
loading: false,
data: [],
}, action) => {
switch(action.type) {
case CONST.FETCH_GITHUB_SEARCH_USER_LOADING:
return {
...state,
loading: true
}
case CONST.FETCH_GITHUB_SEARCH_USER_SUCCESS:
return {
loading: false,
data: action.payload.items
}
default:
return state
}
}
重点是 ...state
这样的解构写法,实际是浅复制了 state 对象,而在 dart 中没有这么方便的方法,比如 reducers.dart 中的 copyWith
:
AppState counterReducer(AppState state, dynamic action) {
switch (action) {
case INCREMENT:
return state.copyWith(count: state.count + 1);
case DECREMENT:
return state.copyWith(count: state.count - 1);
}
return state;
}
copyWith
是我们自己写的扩展方法:
AppState copyWith({count, clickCount}) {
return AppState(
count: count ?? this.count,
clickCount: clickCount ?? this.clickCount,
);
}
??
是条件表达式,count: count ?? this.count
表示 copyWith 只要没传进来 count 就把 AppState 实例的 count 值赋给 count,然后再实例化一个 AppState 对象。
参见其他的复制对象的方案
3. actions
比较有特点的就是 redux_thunk 的异步 action 写法:
ThunkAction asyncIncrement() {
return (Store store) async {
await Future.delayed(Duration(seconds: 3)); // 延迟 3 秒
store.dispatch(INCREMENT);
};
}
async / await 是不是很熟悉?
4. StoreProvider / StoreConnector
StoreProvider 很简单,就是在根组件中挂载 store:
@override
Widget build(BuildContext context) {
return StoreProvider<AppState>(
store: store,
child: MaterialApp(
theme: ThemeData.dark(),
home: Home(),
),
);
}
而 StoreConnector 就比较麻烦了,需要先定义一个 ViewModel:
class AppStateViewModel {
final AppState state;
final void Function() onClick;
AppStateViewModel({
this.state,
this.onClick,
});
}
ViewModel 中规定你要在这个组件中使用的 Store 中的数据
class AddButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, AppStateViewModel>(
converter: (store) {
return AppStateViewModel(onClick: () {
store.dispatch(INCREMENT);
store.dispatch(CLICK);
});
},
builder: (context, vm) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: FloatingActionButton(
onPressed: vm.onClick, // trigger!
tooltip: 'Increment',
child: Icon(Icons.exposure_plus_1),
),
);
},
);
}
}
StoreConnector 有两个方法:
converter
: 使用给定的转换器函数将存储转换为 ViewModel,并将 ViewModel 传递给 builder 函数builder
: 承接converter
返回的数据来使用
以 AddButton 组件为例,converter 返回 onClick 函数来触发 dispatch, builder 承接到这个函数,这样就能在发起 dispatch,走 redux 的流程了。
本文只介绍 redux 在 flutter 中的应用,如果你对 React 很熟,你应该能明白 redux 的意义,除了有个全局 store 外,深层组件之间通信也不是问题了,对于 flutter 这种框架也是很有意义的。
当然,数据管理方案上直接使用 redux 可能会比较重,这和 react 是类似的,小应用的话可以用全局事件来处理。但是,多人协作的大项目 redux 是你最应该想到的数据管理方案。