models(redux) 封装

基于redux进行封装,不改变redux源码,可以结合使用redux社区中其他解决方案。

models用于管理数据,解决的问题:

  • 命名空间(防止数据、方法命名冲突):数据与方法,都归属于具体model,比如:state.userCenter.xxx,this.props.action.userCenter.xxx();
  • 如何方便的获取数据:connect与组件连接;@connect(state => ({name: state.user.name}));
  • 如何方便的修改数据:this.props.action中方法;
  • 客户端数据持久化(保存到LocalStorage中):syncStorage配置;
  • 异步数据处理:基于promise异步封装;
  • 请求错误提示:error处理封装,errorTip配置,自动提示;
  • 请求成功提示:successTip配置,自动提示;
  • 简化写法:types actions reducers 可以在一个文件中编写,较少冲突,方便多人协作,参见models/page.js中的写法;
  • 业务代码可集中归类:在models目录中统一编写,或者在具体业务目录中,模块化方式。

all-models.js

此文件通过脚本自动生成,不要直接编辑,生成规则如下:

  1. /path/to/models/user-center.js --> export userCenter from '/path/to/models/user-center';
  2. /path/to/user-center.model.js --> export userCenter from '/path/to/user-center.model.js';
  3. /path/to/user-center/model.js --> export userCenter from '/path/to/user-center/model.js';

组件与redux进行连接

提供了两种方式,装饰器方式、函数调用方式;

装饰器

推荐使用装饰器方式

  1. import {connect} from 'path/to/models';
  2. @connect(state => {
  3. return {
  4. ...
  5. }
  6. })
  7. class Demo extends Component{
  8. ...
  9. }

函数

  1. import {connectComponent} from 'path/to/models';
  2. class Demo extends Component {
  3. ...
  4. }
  5. function mapStateToProps(state) {
  6. return {
  7. ...
  8. };
  9. }
  10. export default connectComponent({LayoutComponent: Demo, mapStateToProps});

简化写法

action reducer 二合一,省去了actionType,简化写法;

注意:

  • 所有的reducer方法,无论是什么写法中的,都可以直接返回新数据,不必关心与原数据合并(…state),封装内部做了合并;
  • 一个model中,除了initialState syncStorage actions reducers 等关键字之外的属性,都视为action reducer合并写法;

一个函数

一个函数,即可作为action方法,也作为reduce使用

  • 调用action方法传递的数据将不会做任何处理,会直接传递给 reducer
  • 只能用第一个参数接收传递过来的数据,如果多个数据,需要通过对象方式传递,如果不需要传递数据,但是要使用state,也需要定义一个参数占位
  • 第二个参数固定为state,第三个参数固定为action,不需要可以缺省(一般都是缺省的)
  • 函数的返回值为一个对象或者undefined,将于原state合并,作为store新的state
  1. // page.model.js
  2. export default {
  3. initialState: {
  4. title: void 0,
  5. name: void 0,
  6. user: {},
  7. toggle: true,
  8. },
  9. setTitle: title => ({title}),
  10. setName: (name, state, action) => {
  11. const {name: prevName} = state;
  12. if(name !== prevName) return {name: 'Different Name'};
  13. },
  14. setUser: ({name, age} = {}) => ({user: {name, age}}),
  15. setToggle: (arg, state) => ({toggle: !state.toggle}),
  16. }
  17. // 使用
  18. this.props.action.page.setTitle('my title');

数据同步

通过配置的方式,可以让redux中的数据自动与localStorage同步

  1. export default {
  2. initialState: {
  3. title: '',
  4. show: true,
  5. user: {},
  6. users: [],
  7. job: {},
  8. total: 0,
  9. loading: false,
  10. ...
  11. },
  12. // initialState会全部同步到localStorage中
  13. // syncStorage: true,
  14. // 配置部分存数据储到localStorage中
  15. syncStorage: {
  16. titel: true,
  17. user: { // 支持对象指定字段,任意层次
  18. name: true,
  19. address: {
  20. city: true,
  21. },
  22. },
  23. job: true,
  24. users: [{name: true, age: true}], // 支持数组
  25. },
  26. }

action reducer 合并写法

如果action有额外的数据处理,并且一个action 只对应一个reducer,这种写法不需要指定actionType,可以有效简化代码;

  1. export default {
  2. initialState: {
  3. title: '',
  4. ...
  5. },
  6. arDemo: {
  7. // 如果是函数返回值将作为action.payload 传递给reducer,如果非函数,直接将payload的值,作为action.payload;
  8. payload(options) {...},
  9. // 如果是函数返回值将作为action.meta 传递给reducer,如果非函数,直接将meta的值,作为action.meta;
  10. meta(options) {...},
  11. reducer(state, action) {
  12. returtn {...newState}; // 可以直接返回要修改的数据,内部封装会与原state合并`{...state, ...newState}`;
  13. },
  14. },
  15. };

异步action写法

  1. export default {
  2. initialState: {
  3. title: '',
  4. ...
  5. },
  6. fetchUser: {
  7. // 异步action payload 返回promise
  8. payload: ({params, options}) => axios.get('/mock/users', params, options),
  9. // 异步action 默认使用通用异步meta配置 commonAsyncMeta,对successTip errorTip onResolve onReject onComplete 进行了合理的默认值处理,需要action以对象形式传参调用
  10. // meta: commonAsyncMeta,
  11. // meta: {
  12. // successTip: '查询成功!欧耶~',
  13. // errorTip: '自定义errorTip!马丹~',
  14. // },
  15. // meta: () => {
  16. // return {...};
  17. // },
  18. // 基于promise 异步reducer写法;
  19. reducer: {
  20. pending: (state, action) => ({loading: true}),
  21. resolve(state, {payload = {}}) {
  22. const {total = 0, list = []} = payload;
  23. return {
  24. users: list,
  25. total,
  26. }
  27. },
  28. complete: (state, action) => ({loading: false}),
  29. }
  30. },
  31. };

调用方式:

  1. this.props.action.user
  2. .fetchUser({
  3. params,
  4. options,
  5. successTip,
  6. errorTip,
  7. onResolve,
  8. onReject,
  9. onComplete
  10. });

参数约定为一个对象,各个属性说明如下:

参数说明
params请求参数
options请求配置
successTip成功提示信息
errorTip错误提示信息
onResolve成功回调
onReject失败回调
onComplete完成回调,无论成功、失败都会调用

单独定义action 和 reducer

支持这种比较传统的写法,一般也不会太用到

  1. import {createAction} from 'redux-actions';
  2. export const types = {
  3. GET_MENU_STATUS: 'MENU:GET_MENU_STATUS', // 防止各个模块冲突,最好模块名开头
  4. };
  5. export default {
  6. initialState: {
  7. title: '',
  8. ...
  9. },
  10. // 单独action定义,需要使用actionType与reducer进行关联
  11. actions: {
  12. getMenuStatus: createAction(types.GET_MENU_STATUS),
  13. },
  14. // 单独reducer定义,使用了actionType,不仅可以处理当前model中的action
  15. // 也可以处理其他任意action(只要actionType能对应)
  16. reducers: {
  17. [types.GET_MENU_STATUS](state) {
  18. ...
  19. return {
  20. ...
  21. };
  22. }
  23. },
  24. }