flutter 是由谷歌公司于 2015 年推出的移动 UI 框架。其选用 Dart 做为开发语言,内置 Material Design 和 Cupertino 两种设计风格 Widget 部件,使得它能够快速在 ios、android 设备上构建高效、高品质、高流畅度的用户 UI 界面。html
Flutter 在不少设计理念上参考了 React 的设计理念,所以在状态管理库的选择和业务流处理上,也能够选择和 React 相似的解决方案,其中最具表明的即是 Redux。android
在声明式 UI 组件开发的过程当中,一个 Flutter 组件呈现出树状结构,抽象出来大概长这样: ios
能够发现,这种结构下,咱们经过参数去传递一些数据,由父组件向子组件传递数据,子组件负责根据数据进行 UI 组件的渲染,这种数据流是自上而下流动的。编程
可是有时,咱们会须要在 app 的不一样界面中共享应用程序的某些状态或者是数据。若是他们没有任何关系,咱们可能须要不少额外操做来作组件之间的通信,传递这些数据。这时候,咱们便须要一种独立在组件以后的数据源和状态库,来管理这些公共状态和数据。redux
在 Flutter 项目中,应用的状态分为短时状态和应用状态:api
短时状态也称局部状态。好比一个 tab 选项卡中,当前被选中的 tab 对应的序列号;一个输入框当前输入的值均可以称为短时状态(局部状态)。在通常场景下,对于这类状态,每每不是其余组件所关心的,也不须要咱们帮助用户记住这种状态。即便应用重启,这些状态恢复到默认状态,也不会对应用形成太大影响,这种状态的丢失是能够接受的。数组
应用状态,也称共享状态,全局状态等。最具表明性的,即是 loginFlag(一个用于标识用户当前是否登陆的字段)了。这种状态,每每影响了整个应用的逻辑和UI的渲染,由于用户是否登陆决定咱们是否返回当前用户的我的信息等。并且不少时候,登陆状态一旦设置,咱们可能在一段时间内要记住这种状态,即便应用重启,这种状态也不可丢失。相似这种的状态和数据,便称为应用状态。服务器
在 Flutter 中,可提供应用状态管理的工具和第三方组件库有不少,如:Redux, Provider, BloC, RxDart 等。此次记录主要提供以下三种状态库的介绍及使用:markdown
咱们经过使用 Redux,BloC 及 Provider 从分别完成一个数据流的传递,来对比这三者的使用。app
实现的效果以下:
导入依赖
使用 Redux ,咱们须要先导入使用 Dart 编写的 Redux 状态库,还须要导入用于链接 Flutter 应用和Redux状态的连接库flutter-redux:
在 pubspec.yml 文件中导入依赖, 并在命令行运行 flutter pub get
从远程服务器获取依赖:
设计状态模型 Model
根据前面的需求概述,咱们的应用状态根 AppState 中应至少包含下面两个模块的状态:
* 全局状态 => globleState 用于保存全局的 **应用状态**
* 用户状态 => userState 用于保存用户相关的**应用状态**
复制代码
生成状态 UserState model 类
依据前面对应用状态树的设计,咱们首先完成 UserState Model 类的创建:
新建 UserState model 类:
/// model/user_model.dart
/// store user state
class UserState {
String name;
String email;
double age;
UserState({
@required this.name,
@required this.email,
@required this.age
});
}
复制代码
在使用 Redux 进行状态管理时,一般会须要给应用的状态一些默认值,所以能够经过命名构造函数为 UserState 提供一个用于初始化的构造函数 initState:
/// model/user_model.dart
class UserState {
String name;
String email;
double age;
UserState({
@required this.name,
@required this.email,
@required this.age
});
UserState.initState(): name = 'redux', email = 'redux@gmail.com', age = 10;
}
复制代码
经过构造方法,咱们即可以在合适的地方调用 initState 构造函数为 UserState 类提供默认值。
使用过 Redux 的都知道,在 Redux 中,全部的状态都由 Reducer 纯函数生成,Reducer 函数经过接受新、旧状态进行合并,生成新的状态返回到状态树中。 为了防止咱们上一次的状态丢失,咱们应该将上一次的状态记录下来并与新状态进行合并处理,所以咱们还须要在 UserState 类中添加一个 copy 方法用于状态的合并:
关于纯函数能够参考函数式编程
/// model/user_model.dart
class UserState {
String name;
String email;
double age;
UserState({
@required this.name,
@required this.email,
@required this.age
});
UserState.initState(): name = 'redux', email = 'redux@gmail.com', age = 10;
UserState copyWith(UserModel userModel) {
return UserState(
name: userModel.name ?? this.name,
email: userModel.email ?? this.email,
age: userModel.age ?? this.age
);
}
}
复制代码
咱们在类中编写了一个 copyWith 方法,这个方法针对当前实例,接受用户信息 user model, 经过判断是否有新值传入来决定是否返回老状态。
这样一个 UserState 的类便建立好了。
编写 GlobalState, AppState model 类
与 UserState 相似,咱们快速完成 GlobalState, AppState 类
GlobalState model 类:
/// model/global_model.dart
import 'package:flutter/material.dart';
/// store global state
class GlobalState {
bool loginFlag;
GlobalState({
@required this.loginFlag
});
GlobalState.initState(): loginFlag = false;
GlobalState copyWith(loginFlag) {
return GlobalState(
loginFlag: loginFlag ?? this.loginFlag
);
}
}
复制代码
App State model 类:
/// model/app_model.dart
import 'package:flutter_state/Redux/model/global_model.dart';
import 'package:flutter_state/Redux/model/user_model.dart';
/// APP global
class AppState {
UserState userState;
GlobalState globalState;
AppState({ this.userState, this.globalState });
AppState copyWith({
UserState userState,
GlobalState globalState,
}) {
return AppState(
userState: userState ?? this.userState,
globalState: globalState ?? this.globalState
);
}
}
复制代码
创建 store 仓库
接下里,咱们须要在项目根目录中建立一个 store 文件夹,用于存放项目中所须要的 action 和 reducer 文件:
* - store
* - action.dart
* - reducer.dart
复制代码
编写 action
依据前面的需求,咱们在 action 中编写项目中须要用到的 action 动做类。
// action.dart
import 'package:flutter_state/Redux/model/user_model.dart';
// User Action
enum UserAction {
SetUserInfo,
ClearUserInfo,
}
class SetUserInfo {
final UserModel userModel;
SetUserInfo(this.userModel);
}
class ClearUserInfo {}
// Global Action
enum GlobalAction {
SetLoginFlag,
LogoutSystem
}
class SetLoginFlag {
final bool loginFlag;
SetLoginFlag({ this.loginFlag });
}
class LogoutSystem {}
复制代码
一般状况下,一个 Action 动做由 Type 和 Payload 组成,Type 标识动做类型,Payload 做为函数载体。 因为 dart 静态语言的一些特性,使用类来做为数据载体,方便拓展和进行数据逻辑处理。 用类名、字符串仍是枚举来定义你的 Action 动做类型,决定了你在 Reducer 中如何去判断 Action 的动做类型进而进行相关的逻辑处理。实际业务中可根据业务场景灵活处理。
定义好相关的 action 动做后,咱们编写对应的 reducer 函数。前面提到过,Reducer 函数经过接受新、旧状态进行合并,生成新的状态返回到状态树中:
// reducer.dart
...
import 'package:redux/redux.dart';
UserState userSetUpReducer(UserState userState, action) {
if (action is SetUserInfo) {
return userState.copyWith(action.userModel);
} else if (action is ClearUserInfo) {
return UserState.initState();
} else {
return userState;
}
}
GlobalState globalStatusReducer(GlobalState globalState, action) {
if (action is SetLoginFlag) {
return globalState.copyWith(action.loginFlag);
} else if (action is LogoutSystem) {
return GlobalState.initState();
} else {
return globalState;
}
}
复制代码
上面的代码中,分别定义了两个纯函数 userSetUpReducer, globalStatusReducer。他们的逻辑很是简单,经过判断 action 动做类型,对相应的 State 进行合并操做,生成新的状态并返回。
因为咱们使用`类`去做为 Action 进行派发,所以在 reducer 中处理对应 action 时,可经过 is 来判断类的类型
编写顶层 appReducer 函数
完成子模块 reducer 函数的编写后,咱们须要完成组件状态树的顶层函数的 appReducer。appReducer 维护了咱们应用最顶层状态,咱们在此处将对应的模块状态交给他们的 reducer 函数进行处理:
import 'package:flutter_state/Redux/model/app_model.dart';
import 'package:flutter_state/Redux/store/reducer.dart';
AppState appReducer(AppState appState, action) {
return appState.copyWith(
userState: userReducers(appState.userState, action),
globalState: globalReducers(appState.globalState, action),
);
}
复制代码
appReducer 函数,接受 AppState,并经过 copyWith 方法,将 userState 和 globalState 状态分别交由他们对应的 reducer 函数进行处理。
复制代码
在应用中关联 store
通常场景下,咱们只在业务最顶层维护一个全局的 store , 顶层的 store 经过 接受 reducer 函数来进行状态的合并与分发处理
接下来,咱们在 应用入口处初始化 store 仓库,并绑定到应用中:
// main.dart
...
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
// before
void main() {
runApp(MyApp())
};
// after
void main() {
final store = Store<AppState>(
appReducer,
initialState: AppState(
globalState: GlobalState.initState(),
userState: UserState.initState(),
)
);
runApp(
StoreProvider<AppState>(
store: store,
child: MyApp(),
)
);
}
...
复制代码
上面的代码,经过 Redux 中 store, 咱们初始化了一个 store 仓库,在 initialState 里咱们设置了应用的初始状态。
以后咱们经过 flutter_redux 中 StoreProvider 方法,将 store 和 应用(MyApp)进行了关联。
这样咱们的 store 仓库便导入完成了。
创建 UI 测试组件
新建 redux_perview 组件, 在其中完成视图的编辑:
// redux_perview.dart
class ReduxPerviewPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
....
child: Column(
children: <Widget>[
Text('Redux Perview: ', style: TextStyle(fontSize: 40),),
SizedBox(height: 40,),
Text('Name: ', style: _textStyle),
Text('Email: ', style: _textStyle),
Text('Age: ', style: _textStyle),
]
),
}
}
复制代码
Perview 负责对用户的信息进行展现,当用户没有登陆时,该组件隐藏并使用 store 默认值。
新建 redux_trigger 组件,再其完成用于用户输入 UI 的绑定:
// redux_trigger
class ReduxTriggerPage extends StatelessWidget {
static final formKey = GlobalKey<FormState>();
final UserModel userModel = new UserModel();
Widget _loginForm (BuildContext context, Store) {
...
Column(
children: [
...
TextFormField(
decoration: InputDecoration(labelText: 'Name'),
onSaved: (input) => userModel.name = input,
),
...
]
)
}
@override
Widget build(BuildContext context) {
return _loginForm(context)
}
}
复制代码
Trigger 组件接受用户输入的信息,提交到 store 仓库。该组件在输入完毕登陆成功以后,处于影藏状态
此时运行效果以下:
在 ReduxPerviewPage 组件中使用状态
接下来,咱们要在 UI 组件中绑定仓库中的状态。
Flutter_redux 为咱们提供了两个函数组件 StoreConnector 和 StoreBuilder,在文章的最后会针对这两个方法的使用场景作进一步的介绍。
在此处,咱们使用 StoreBuilder 完成对 perview 展现页面的绑定:
为了防止嵌套过深,将 UI 部分抽离为 _perviewWidget 方法
class ReduxPerviewPage extends StatelessWidget {
Widget _perviewWidget(BuildContext context, Store<AppState> store) {
...
UserState userState = store.state.userState;
...
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
...
Text('Name: ${userState.name}', style: _textStyle),
]
)
}
@override
Widget build(BuildContext context) {
return StoreBuilder<AppState>(
builder: (BuildContext context, Store<AppState> store) =>
store.state.globalState.loginFlag ? _perviewWidget(context, store) : Center(child: Text('请登陆'),)
);
}
}
复制代码
上面的代码中,咱们使用 StoreBuilder 的 builder 方法,拿到了上下文 context 和 store 的仓库的状态。经过逻辑判断,将状态传入 _perviewWidget 完成页面 Store 状态至 UI 数据的绑定。
在 ReduxTrggier 改变页面状态
接下来咱们在 trigger 组件中提交 action 信息,来改变 state 中的状态:
trigger 组件
class ReduxTriggerPage extends StatelessWidget {
static final formKey = GlobalKey<FormState>();
final UserModel userModel = new UserModel();
Widget _loginForm (BuildContext context, Store<AppState> store) {
return Center(
child: Container(
height: (MediaQuery.of(context).size.height - 120) / 2,
padding: EdgeInsets.symmetric(vertical: 20, horizontal: 30),
child: Form(
key: formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Name'),
onSaved: (input) => userModel.name = input,
),
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
onSaved: (input) => userModel.email = input,
),
TextFormField(
decoration: InputDecoration(labelText: 'Age'),
onSaved: (input) => userModel.age = double.parse(input),
),
FlatButton(
onPressed: () {
formKey.currentState.save();
// 提交 action 动做
StoreProvider.of<AppState>(context).dispatch(new SetLoginFlag(loginFlag: true));
StoreProvider.of<AppState>(context).dispatch(new SetUserInfo(userModel));
formKey.currentState.reset();
},
child: Text('递交信息'),
color: Colors.blue,
textColor: Colors.white,
)
]
),
),
)
);
}
@override
Widget build(BuildContext context) {
return StoreBuilder<AppState>(
builder: (BuildContext context, Store<AppState> store) =>
store.state.globalState.loginFlag ? Text('') : _loginForm(context, store)
);
}
}
复制代码
上面的代码中,咱们在 StatelessWidget widget 无状态组件中使用form 表单,对实例化的 userModel 对象进行赋值。 使用 Flutter_redux 提供的 StoreProvider 类,经过调用 of 静态方法,即可以拿到 store 实例
拿到实例之后,即可经过 dispatch 方法发送对应的 action,redux 接受到 action 以后,便会交由 reducer 函数进行处理了。
StoreProvider 经过实现 InheritedWidget 机制实现,原理相似 redux 中的 context,当 store 发生改变的时候,StoreConnector 或者 StoreBuilder 状态的获得最新状态,此时经过 StoreConnector 或 StoreBuilder 包裹的组件便都会获得更新。
上面 redux_perview 例子,使用 StoreConnector 重构:
// redux_perview.dart
class ReduxPerviewPage extends StatelessWidget {
Widget _perviewWidget(BuildContext context, AppState appState) {
UserState userState = appState.userState;
return
...
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlutterLogo(),
Text('Redux Perview:', style: TextStyle(fontSize: 40),),
SizedBox(height: 40,),
Text('Name: ${userState.name}', style: _textStyle),
Text('Email: ${userState.email}', style: _textStyle),
Text('Age: ${userState.age}', style: _textStyle),
...
]
),
...
}
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, AppState>(
converter: (Store<AppState> store) => store.state,
builder: (BuildContext context, AppState appState) =>
appState.globalState.loginFlag ? _perviewWidget(context, appState) : Center(child: Text('请登陆'),)
);
}
}
复制代码
StoreConnector 调用 converter 函数,将 Store 的映射为 appState。 经过 StoreConnector,咱们能够对 store 的参数进行处理,映射为组件须要的状态供组件进行使用。
StoreConnector 接受两个泛型参数:
经过源码,能够看到第一个泛型,须要传入 Store 声明,第二参数能够传入你须要映射的类型。在 Flutter_redux 内部,converter 方法会在 initState 应用钩子初始化的时候调用:
有了这层转换,咱们即可以去作逻辑层的抽象,将咱们的实例映射成 viewModel,这样即可进一步对逻辑层进行抽离,前面 redux_perview 例子,咱们作以下改造:
新建一个 PerviewViewModel 类:
class PerviewViewModel {
final UserState userModel;
final bool loginFlag;
final Function() clearUserInfo;
final Function() logout;
PerviewViewModel({
this.userModel,
this.loginFlag,
this.clearUserInfo,
this.logout
});
factory PerviewViewModel.create(Store<AppState> store) {
_clearUserInfo() {
store.dispatch(new ClearUserInfo());
}
_logout() {
store.dispatch(new LogoutSystem());
}
return PerviewViewModel(
userModel: store.state.userState,
loginFlag: store.state.globalState.loginFlag,
clearUserInfo: _clearUserInfo,
logout: _logout
);
}
}
复制代码
在 previewModelView 中,咱们经过构造函数 create 传入 store 实例,将 store 和 UI 相关的业务,所有抽离到 viewModel 当中。修改 converter 方法,将映射类型修改成 previewModelView:
...
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, PerviewViewModel>(
converter: (Store<AppState> store) => PerviewViewModel.create(store),
builder: (BuildContext context, PerviewViewModel model) =>
model.loginFlag ? _perviewWidget(context, model) : Center(child: Text('请登陆'),)
);
}
...
复制代码
此时,咱们传入的 UI 组件的数据变动为 PerviewViewModel 实例,修改 _perviewWidget Ui 组件代码:
...
Widget _perviewWidget(BuildContext context, PerviewViewModel model) {
...
FlutterLogo(),
Text('Redux Perview:', style: TextStyle(fontSize: 40),),
SizedBox(height: 40,),
Text('Name: ${model.userModel.name}', style: _textStyle),
Text('Email: ${model.userModel.email}', style: _textStyle),
Text('Age: ${model.userModel.age}', style: _textStyle),
FlatButton(
onPressed: () {
model.clearUserInfo();
model.logout();
},
child: Text('logout'),
color: Colors.grey,
textColor: Colors.black,
)
...
复制代码
能够发现,咱们经过 viewModel 的形式,将本来耦合在业务中逻辑代码,抽离到 viewModel 中,针对于对 store 状态的组合和逻辑代码,就能够与UI 组件进行解耦了。
这种模式在应对一些复杂的业务过程当中,能够有效的帮助咱们去解绑 UI 和 store 层。将逻辑和 UI 分离,不只仅有利于咱们保持 UI 组件的简洁,针对 viewModel 的逻辑,更方便了咱们去作业务的单元测试。在将来业务修改和移植的时候,也会更加清晰。
经过上面的案例能够发现,使用 StoreConnector 能够有效解耦业务,在一些简单的场景下,使用 StoreConnector 可能让代码量增多。所以在使用 StoreConnector 仍是 StoreBuilder 上,我以为在一些简单场景下,咱们应该尽量抽取 UI 组件,对状态的设计和数量要有必定控制,这时即可以使用 StoreBuilder 直接处理 store 相关逻辑。
可是对于一些复杂的业务场景,须要频次对 store 进行操做的时候,为了往后组件的复用及代码清晰度,可使用 StoreConnector 对业务层进行抽象,这样对往后的维护有很大好处。
在业务开发的过程当中,咱们能够在 viemModel 中处理咱们的业务逻辑。可是对于一些异步问题,如 service api的调用,应该在何处进行呢?redux 基于一种通用的设计,解决了异步 Action 的调用问题,那即是加入 middleware(中间件)。
到底什么是中间件呢?
中间件实际上是负责业务反作用,并处理业务逻辑相关工做(一般是异步的)的实体。 全部的 action 在到达 reducer 函数前都会通过中间件。每一个中间件经过 next 函数,调用下一个中间件,并在 next 中传递 action 动做,进而完成由 中间件 => reducer => 中间件 的调度过程。
中间件使用示例
咱们结合两个场景来演示中间件的使用。
前面在 redux_trggier 组件中,咱们经过直接触发 setLoginFlag action 来完成了登陆状态的设置。事实上在真实业务场景中,咱们应先对 setUserInfo action 中的入参进行必定的校验后,在发送给服务器进行身份验证。经过 http 请求拿到后台返回的约定状态码后,再根据状态码判断用户是否登陆成功并触发对应的 action
针对这个业务场景,咱们使用中间件来解决。
首先 action 中新增一些 新的 action 动做用与 action 的派发和相关业务:
// store/action.dart
// 用户承载页面入参的action
class ValidateUserLoginFields {
final UserModel userModel;
ValidateUserLoginFields(this.userModel);
}
// 用户承载入参错误信息的action
class LoginFormFieldError {
final String nameErrorMessage;
final String emailErrorMessage;
final String ageErrorMessage;
LoginFormFieldError(
this.nameErrorMessage,
this.emailErrorMessage,
this.ageErrorMessage
);
}
// 用于发送用户信息的 action
class FetchUserLogin {
final UserModel userModel;
FetchUserLogin(this.userModel);
}
// 用于清空错误信息的 action
class ClearUserLoginErrorMessage {}
复制代码
咱们新增了上述的 4 个 action 来处理咱们的业务场景。修改 redux_trigger 组件,并新增 TriggerViewModel 来关联咱们的组件和store:
// screen/redux_trigger.dart
class TriggerViewModel {
final String nameErrorMessage;
final String emailNameError;
final String ageNameError;
final bool loginFlag;
final Function(UserModel) fetchUserLogin;
TriggerViewModel({
this.nameErrorMessage,
this.emailNameError,
this.ageNameError,
this.loginFlag,
this.fetchUserLogin
});
factory TriggerViewModel.create(Store<AppState> store) {
_fetchUserLogin(UserModel userModel) {
// store.dispatch(new ClearUserLoginErrorMessage());
store.dispatch(new SetLoginFlag(loginFlag: true));
}
return TriggerViewModel(
nameErrorMessage: store.state.userState.nameErrorMessage,
emailNameError: store.state.userState.emailErrorMessage,
ageNameError: store.state.userState.ageErrorMessage,
loginFlag: store.state.globalState.loginFlag,
fetchUserLogin: _fetchUserLogin
);
}
}
复制代码
修改 redux_trigger build 方法,并在 UI 中增长错误提示组件:
...
model.emailNameError.isNotEmpty ? Text(model.emailNameError, style: textStyle) : Container(),
TextFormField(
decoration: InputDecoration(labelText: 'Age'),
onSaved: (input) => userModel.age = input,
),
model.ageNameError.isNotEmpty ? Text(model.ageNameError, style: textStyle) : Container(),
FlatButton(
onPressed: () {
formKey.currentState.save();
model.fetchUserLogin(userModel);
// formKey.currentState.reset();
},
child: Text('递交信息'),
color: Colors.blue,
textColor: Colors.white,
)
...
复制代码
接下来,咱们在 store/ 目录下,新增 middleware 文件用于放置中间件,并新增 AuthorizationMiddleware 类用于登陆鉴权相关业务的处理与 action 派发:
// store/middlewares.dart
class AuthorizationMiddleware extends MiddlewareClass<AppState> {
void validateUserInfo(UserModel userModel, NextDispatcher next) {
Map<String, String> errorMessage = new Map<String, String>();
if (userModel.name.isEmpty) {
errorMessage['nameErrorMessage'] = '姓名不能为空';
}
if (userModel.email.length < 10) {
errorMessage['emailErrorMessage'] = '邮箱格式不正确';
}
if (userModel.age.toString().isNotEmpty && int.parse(userModel.age) < 0) {
errorMessage['ageErrorMessage'] = '年龄不能为负数';
}
if (errorMessage.isNotEmpty) {
next(
new LoginFormFieldError(
errorMessage['nameErrorMessage'],
errorMessage['emailErrorMessage'],
errorMessage['ageErrorMessage'],
)
);
} else {
next(new SetLoginFlag(loginFlag: true));
}
}
@override
void call(Store<AppState> store, dynamic action, NextDispatcher next) {
if (action is ValidateUserLoginFields) {
validateUserInfo(action.userModel, next);
}
}
}
复制代码
AuthorizationMiddleware 类,继承了 MiddlewareClass, 咱们重写他的 call 方法,并在其中去作 action 动做的过滤,当发送动做为 ValidateUserLoginFields 时,调用 validateUserInfo 方法对入参进行校验。咱们将对应的 action 传递到 Next 函数中,发送给下一个中间件。
在 store/middlewares 下,管理相关的中间件:
List<Middleware<AppState>> createMiddlewares() {
return [
AuthorizationMiddleware()
];
}
复制代码
在 main.dart 中初始化中间件:
final store = Store<AppState>(
appReducer,
middleware: createMiddlewares(),
initialState: AppState(
globalState: GlobalState.initState(),
userState: UserState.initState(),
)
);
复制代码
前面咱们提到了中间件经过 next 函数完成由 中间件 -> reducer -> 中间件 这个调度过程的,回头看看 AuthorizationMiddleware 的方法你会发现当 action 动做并不是是 ValidateUserLoginFields 时,AuthorizationMiddleware 并无将 action 继续向后传递交给下一个中间件。这便致使了整个调度过程的中止,修改 call 方法:
....
@override
void call(Store<AppState> store, dynamic action, NextDispatcher next) {
if (action is ValidateUserLoginFields) {
validateUserInfo(action.userModel, next);
}
next(action)
}
复制代码
能够看到这时的运行效果:
接下来,修改 AuthorizationMiddleware 中间件处理异步问题:
/// 模拟 service 异步请求
void fetchUserLogin(Store<AppState> store, UserModel userModel) async {
UserModel model = await Future.delayed(
Duration(milliseconds: 2000),
() {
return new UserModel(name: '服务返回name', age: 20, email: 'luma@qq.com');
}
);
if (model != null) {
store.dispatch(new SetUserInfo(model));
store.dispatch(new SetLoginFlag(loginFlag: true));
}
}
class AuthorizationMiddleware extends MiddlewareClass<AppState> {
void validateUserInfo(Store<AppState> store, UserModel userModel, NextDispatcher next) {
if (userModel.name.isEmpty) {
...
} else {
fetchUserLogin(store, userModel);
}
}
...
}
复制代码
当请求校验经过后,便在 middleware 中调用 fetchUserLogin 方法请求后台 api 接口,根据返回值处理用户信息了。
此时运行效果以下:
把全部的业务放到中间件来作也不是惟一的选择,有时候咱们可能会在 viewModel 去处理各种校验或业务,咱们的 action 可能会包含一些反作用。如何处理带反作用的 action 呢?咱们能够借助 redux_thunk 这个组件来处理异步action
首先在 pubspec.yaml 引入 redux_thunk
修改 store/action.dart,新增一个异步 action:
class ReduxThunkLoginFetch {
static ThunkAction<AppState> fetchUserLogin(UserModel userModel) {
return (Store<AppState> store) async {
UserModel model = await Future.delayed(
Duration(milliseconds: 2000),
() {
return new UserModel(name: '服务返回name', age: 20, email: 'luma@qq.com');
}
);
if (model != null) {
store.dispatch(new SetUserInfo(model));
store.dispatch(new SetLoginFlag(loginFlag: true));
}
};
}
}
复制代码
能够看到,咱们在一个 ReduxThunkLoginFetch action 类中,增长了一个静态方法,该方法处理了与以前 AuthorizationMiddleware 中同样的方法,所不一样的是,这个方法被标识为一个 ThunkAction , 由于他内部返回了 Future.
此时在 redux_trggier 中,即可以经过调用 ReduxThunkLoginFetch.fetchUserLogin 来获取返回:
/// redux_trigger viewModel
_fetchLoginWithThunk(UserModel userModel) {
// todo 校验
store.dispatch(ReduxThunkLoginFetch.fetchUserLogin(userModel));
}
复制代码
redux-thunk 中间件为咱们拦截了 ThunkAction 类型的 action 动做。当派发动做是一个 ThunkAction 的时候,redux-thunk 会执行这个 action, 并传递 store 和响应的参数到 action 方法中完成异步 action 的调用。
combineReducers 是一个高阶函数,能够帮咱们组合多个 reducer 函数。 并提供了对 reducer action 的一些校验,实际场景中可根据须要使用。
使用 redux 在对 state 状态进行设计的时候,每每咱们但愿的是全局只有一个 state 实例。就拿上面的示例来讲,appState、userState、globalState 他们应该都是全局惟一,不可改变的。在 Dart 中,咱们能够经过对类添加装饰器的模式,来标识咱们的类是 immutable 的:
@immutable
class CountState {
final bool loginFlag;
GlobalState({
@required this.loginFlag
});
GlobalState.initState(): loginFlag = false;
}
复制代码
dart 语法会自动检测被装饰的类,是否具备可变属性(是否有 final 声明)。
有关 immutable 能够查看 immutable 介绍
当声明一个类是 immutable 以后,即可在编译阶段对类的属性进行检测,并可防止其余人对 state 进行修改和混入了。