目录
0. 相关概念
1. 在Flutter中使用Redux
一个用于JavaScript应用的可预测状态管理容器。
0. 相关概念
Redux由Flux演变而来,但受Elm(函数式编程语言)的启发,避开了Flux的复杂性。最初是为了解决React应用组件之间共享状态。
采用单向数据流。这意味着应用中所有的数据都遵循相同的生命周期,这样可以让应用变得更加可预测且容易理解。
组件可以监听状态修改,如果有修改Redux会通知组件,从而使组件重新绘制。
3大原则
- 单一数据源
应用中所有的State都以一个对象树的形式储存在一个唯一的store中(不支持多个)。
store职责:
1. 维持应用的 state;
2. 提供 getState() 方法获取 state;
3. 提供 dispatch(action) 方法更新 state;
4. 通过 subscribe(listener) 注册监听器;
5. 通过 subscribe(listener) 返回的函数注销监听器。
import { createStore } from 'redux'
import todoApp from './reducers'
// 第二个参数是可选的, 用于设置 state 初始状态。
let store = createStore(todoApp);
- State是只读的
不能直接修改State状态对象,惟一改变State的办法是触发Action。
Action是store数据的唯一来源。通过store.dispatch(action)将action传到store。
State可以是基本类型、数组、对象等。
实际开发中,经常会有数据相互引用,在 State 里同时存放 todosById: { id -> todo } 和 todos: array<id> 是比较好的方式。每个数据以 ID 为主键,不同实体或列表间通过 ID 相互引用数据。
Action可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行。
应该尽量减少在 Action 中传递的数据(利用id)。
Action创建函数只是简单的返回一个Action。
// 返回Action
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
可以:
通过dispatch(addTodo(text))来触发Action。
也可以:
const boundAddTodo = text => dispatch(addTodo(text));
boundAddTodo(text);
通过使用指定的 middleware,action 创建函数除了返回 action 对象外还可以返回函数。
当 action 创建函数返回函数时,这个函数会被 Redux Thunk middleware 执行。
这个函数并不需要保持纯净;它还可以带有副作用,包括执行异步 API 请求。
这个函数可以 dispatch action。
这个函数可以有返回值,会被当作 dispatch 方法的返回值传递。
这个函数的结果可以再次被 dispatch。
// thunk action创建函数
// store.dispatch(fetchPosts('reactjs')) 触发Action
export function fetchPosts(subreddit) {
return function (dispatch) {
// 触发Action,通知API请求发起了
dispatch(requestPosts(subreddit))
return fetch(`http://www.subreddit.com/r/${subreddit}.json`)
.then(
response => response.json(),
// 不要使用 catch,因为会捕获在 dispatch 和渲染中出现的任何错误,导致 'Unexpected batch number' 错误。
error => console.log('An error occurred.', error)
)
.then(json =>
dispatch(receivePosts(subreddit, json))
)
}
}
const loggerMiddleware = createLogger()
const store = createStore(
rootReducer,
applyMiddleware(
thunkMiddleware, // 允许thunk函数
loggerMiddleware // 打印 action 日志
)
)
store
.dispatch(fetchPosts('reactjs'))
.then(() => console.log(store.getState())
)
- 使用纯函数来执行修改
为了描述Action如何改变State树,需要编写Reducers函数。
Reducer是一个接收现有State和Action并返回新State的函数。描述了Action如何把现有State转变成新State(全新的对象,而不是修改的旧对象)。
永远不要在Reducer函数中:
1. 修改传入的参数。应该去创建一个副本然后修改并返回。
2. 执行有副作用的操作,如 API 请求和路由跳转
3. 调用非纯函数,如 Date.now() 或 Math.random()
遇到未知的Action时,一定要返回旧的State。
Reducer合成:随着应用不断变大,应该把根级的 Reducer 拆成多个小的 Reducer,分别独立地操作 State 树的不同部分。将拆分后的 reducer 放到不同的文件中, 以保持其独立性并用于专门处理不同的数据域。
方式1
function todoApp(state = {}, action) {
return {
todos: todos(state.todos, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action)
};
}
方式2(等价)
const todoApp = combineReducers({
visibilityFilter,
todos
})
combineReducers只是生成一个函数,这个函数来调用一系列 reducer,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理,然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象。
好处
- 可预测性(一致性)(便于后期维护)
无论是在任何环境(Web端、服务端、原生端),应用的行为都保持一致。
- 中心化
因为对状态的修改集中到了一个地方,从而使得完成诸如撤销/重做、持久化等操作变得容易,
- 可调试性
使用Redux DevTools,可以追踪到应用状态什么时候、什么地方、为什么,以及怎样发生了改变。
- 灵活性
可以同任意的UI层搭配工作。
例
import { createStore } from 'redux';
// reducer
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
// 创建store
let store = createStore(counter);
// 可以手动订阅更新,也可以将事件绑定到视图层。
store.subscribe(() =>
console.log(store.getState())
);
// 触发action更新state
// 触发action后store会把现有state和action传给reducer函数,最后将新state保存在store。
// 约定action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作
store.dispatch({ type: 'INCREMENT' }); // 1
store.dispatch({ type: 'INCREMENT' }); // 2
store.dispatch({ type: 'DECREMENT' }); // 1
1. 在Flutter中使用Redux
只使用Redux来管理全局性或需要持久化的状态,局部性和不需要持久化的状态直接存放在有状态组件的State对象中。
不可变对象如果需要支持修改,可以实现copyWith方法。
第一步:添加依赖库
1. redux库
使用Redux
2. redux_logging库
打印Action日志
3. redux_thunk库
支持异步操作
4. flutter_redux库
StoreConnector组件来获取状态并监听状态修改。StoreProvider直接获得store对象。
5. redux_persist库
持久化状态
6. redux_persist_flutter
第二步:创建相关目录
1. reducers目录
reducers.dart文件(统一处理)
各个子模块reducer.dart
2. actions目录
各个子模块action.dart
3. models目录
state子目录(state.dart、各个子模块state.dart)
创建state
class AppState{
final String version;
// 各个子模块state
final AccountState account;
final PostState post;
final PublishState publish;
final UserState user;
AppState({
String version,
AccountState account,
PostState post,
PublishState publish,
UserState user,
}):
this.version=version??ProjectConfig.packageInfo.version,
this.account=account??AccountState(),
this.post=post??PostState(),
this.publish=publish??PublishState(),
this.user=user??UserState();
AppState copyWith({
String version,
AccountState account,
PostState post,
PublishState publish,
UserState user,
})=>
AppState(
version: version??this.version,
account: account??this.account,
post: post??this.post,
publish: publish??this.publish,
user: user??this.user,
);
factory AppState.fromJson(Map<String,dynamic> json)=>_$AppStateFromJson(json);
Map<String,dynamic> toJson()=>_$AppStateToJson(this);
}
创建 store对象
Store<AppState> getStore(){
if(_store==null){
final List<Middleware<AppState>> wms=[]; // 中间件列表
if(ProjectConfig.isLogAction){ // 需要打印Action日志
// 添加 日志打印中间件
wms.add(LoggingMiddleware<AppState>(logger: getLogger('action'),level: Level.FINE));
}
wms.addAll([
thunkMiddleware, // 添加 异步操作中间件
getPersistor().createMiddleware(), // 添加 持久化状态中间件
]);
//
_store=Store<AppState>(
appReducer, // Action处理器
initialState: AppState(), // 初始状态
middleware: wms, // 中间件列表
);
}
return _store;
}
在应用的上下文添加store对象
Widget build(BuildContext context) {
return StoreProvider<AppState>(
store: store,
child: MaterialApp(
title: ProjectConfig.packageInfo.appName,
theme: ProjectTheme.theme,
routes: {
'/':(context)=>BootstrapPage(),
'/login':(context)=>LoginPage(),
'/register':(context)=>RegisterPage(),
'/tab':(context)=>TabPage(),
},
),
);
}
绑定stroe到widget
class HomePage extends StatelessWidget{
static final _bodyKey=GlobalKey<_BodyState>();
HomePage({
Key key,
}):super(key:key);
@override
Widget build(BuildContext context) {
return StoreConnector<AppState,_ViewModel>(
converter: (store)=>_ViewModel( // store发生变化,会重新执行converter,builder。
postsFollowing:store.state.post.postsFollowing.map<PostEntity>((v) => store.state.post.posts[v.toString()]).toList(),
),
builder: (context,vm)=>Scaffold(
appBar: AppBar(
title: Text('首页'),
),
body: _Body(
key:_bodyKey,
store:StoreProvider.of<AppState>(context),
vm:vm,
),
bottomNavigationBar: ProjectTabBar(tabIndex:0),
),
);
}
}
定义action
class ResetStateAction{
}
中间件
class AccountInfoAction{
final UserEntity user;
AccountInfoAction({
@required this.user,
});
}
ThunkAction<AppState> accountRegisterAction({
@required RegisterForm form,
void Function(UserEntity) onSuccessed,
void Function(NoticeEntity) onFailed,
})=>(Store<AppState> store)async{
final projectService=await ProjectFactory().getProjectService();
final response=await projectService.post(
'/account/register',
data: form.toJson(),
);
if(response.code==ProjectApiResponse.codeOK){
final user=UserEntity.fromJson(response.data['user']);
if(onSuccessed!=null)onSuccessed(user);
}else{
if(onFailed!=null)onFailed(NoticeEntity(message: response.message));
}
};
reducer
AppState appReducer(AppState state,action){
if(action is ResetStateAction){ // 清零(应用置为初始状态)
return AppState();
}else{
return state.copyWith(
account: accountReducer(state.account,action),
publish: publishReducer(state.publish,action),
post: postReducer(state.post,action),
user: userReducer(state.user,action),
);
}
}
account如下:
final accountReducer=combineReducers<AccountState>([
TypedReducer<AccountState,AccountInfoAction>(_accountInfo),
]);
// 更新账户信息
AccountState _accountInfo(AccountState state,AccountInfoAction action){
return state.copyWith(
user: action.user,
);
}
触发action
widget.store.dispatch(ResetStateAction())
StoreProvider.of<AppState>(context).dispatch(accountRegisterAction(
onSuccessed:(UserEntity user){
setState(() {
_isLoading=false;
});
Navigator.of(context).pop(true);
},
onFailed:(NoticeEntity notice){
setState(() {
_isLoading=false;
});
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(notice.message),
duration: notice.duration,
));
},
form:_form,
));
}
网友评论