1 初识Dva

1.1 定义

dva 首先是一个基于reduxredux-saga数据流方案,然后为了简化开发体验,dva 还额外内置了react-routerfetch,所以也可以理解为一个轻量级的应用框架。

1.2 Dva数据流概念

数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State,所以在 dva 中,数据流向非常清晰简明。

img

1.3 model层重要API

  1. namespace

model 的命名空间,同时也是他在全局 state 上的属性,只能用字符串,不支持通过 . 的方式创建多层命名空间。

  1. state

状态初始值

  1. reducers

以 key/value 格式定义 reducer。用于处理同步操作,唯一可以修改 state 的地方。由 action 触发。格式为 (state, action) => newState[(state, action) => newState, enhancer]

  1. effects

以 key/value 格式定义 effect。用于处理异步操作和业务逻辑,不直接修改 state。由 action 触发,可以触发 action,可以和服务器交互,可以获取全局 state 的数据等等。

格式为 *(action, effects) => void[*(action, effects) => void, { type }]

  1. subscriptions

以 key/value 格式定义 subscription。subscription 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action。在 app.start() 时被执行,数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。

格式为 ({ dispatch, history }, done) => unlistenFunction

  1. Action

Action 是一个普通 javascript 对象,它是改变 State 的唯一途径。无论是从 UI 事件、网络回调,还是 WebSocket 等数据源所获得的数据,最终都会通过 dispatch 函数调用一个 action,从而改变对应的数据。action 必须带有 type 属性指明具体的行为,其它字段可以自定义,如果要发起一个 action 需要使用 dispatch 函数;需要注意的是 dispatch 是在组件 connect Models以后,通过 props 传入的。

  1. dispatch函数

dispatching function 是一个用于触发 action 的函数,action 是改变 State 的唯一途径,但是它只描述了一个行为,而 dipatch 可以看作是触发这个行为的方式,而 Reducer 则是描述如何改变数据的。

在 dva 中,connect Model 的组件通过 props 可以访问到 dispatch,可以调用 Model 中的 Reducer 或者 Effects。

2 使用

2.1 路由跳转

  • 基于action进行页面跳转
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { routerRedux } from 'dva/router';

// 在effects中使用
yield put(routerRedux.push('/logout'));

// 在effects外使用
dispatch(routerRedux.push('/logout'));

// 带参数跳转
routerRedux.push({
  pathname: '/logout',
  query: {
    page: 2,
  },
});

3 示例

通过一个简单的表格数据获取与延时删除demo,熟悉dva数据流的概念、结构及主要API

文件目录

  • src
    • model
      • userModel.js
    • service
      • userApi.js
    • page
      • UserPage
        • index.jsx

userApi.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 返回假数据
export function getAllUsers() {
  return {
    content: [
      { id: 1, age: 13, name: 'Jack' },
      { id: 2, age: 32, name: 'Sam' },
      { id: 3, age: 45, name: 'Micheal' },
    ],
  };
}

Index.jsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import React from 'react';
import { connect } from 'dva';
import { Table } from 'antd';

const UserManage = (props) => {
  // 把下面mapStateToProps方法return的数据从props中取出
  // 经过connect后dispatch也会自动传入props
  const { users, dispatch } = props;

  // 获取数据的回调
  const getUsers = () => {
    // 执行dispatch方法,参数为action对象
    dispatch({
      // action对象必须包括type属性,固定写法“命名空间/方法名”,
      // 可以理解为触发该命名空间中某个方法,给方法传递参数可以在后面写key:value形式,没有可以不写
      type: 'user/getAllUsers',
    });
  };
  // 删除数据的回调
  const deleteUser = (id) => {
    dispatch({
      type: 'user/deleteUser',
      // 为model层中的deleteUser方法传入id
      id,
    });
  };
  // 延时删除数据的回调
  const delayRemoveUser = (id) => {
    dispatch({
      type: 'user/delayRemoveUser',
      id,
    });
  };
  // 表格列配置
  const columns = [
    { title: '序号', dataIndex: 'id' },
    { title: '名称', dataIndex: 'name' },
    {
      title: '修改',
      render: (_, record) => [
        // 这里的回调要注意使用箭头函数
        <button onClick={() => deleteUser(record.id)}>删除</button>,
        <button onClick={() => delayRemoveUser(record.id)}>1s后删除</button>,
      ],
    },
  ];
  return (
    <>
      <button type="button" onClick={getUsers}>
        获取数据
      </button>
      <Table rowKey="id" dataSource={users} columns={columns} />
    </>
  );
};

// 将state映射为props
// 参数中的state表示全部model层的state
const mapStateToProps = (state) => {
  // 这里只用到user中的state,所以state.user把命名空间为user这个model层的state数据取出来
  const { users } = state.user;
  // 这里return出去的数据,会变成此组件的props,在组件可以通过props.num取到。
  // props变化了,会重新触发render方法,界面也就更新了。
  return {
    users,
  };
};

// 使用connect连接组件和model层数据
// connect方法用来连接models层的state数据,参数常用的有2个,是第一个mapStateToProps,第二个mapDispatchToProps
// mapStateToProps按字面意思:把models层state数据变为组件的props
// mapDispatchToProps:用了此方法,dispatch只会在此方法里。不写该参数,dispatch会作为组件的props。(我平常用几乎不写该方法)
export default connect(mapStateToProps)(UserManage);
// 简写,将user命名空间下的全部state解构出来作为参数,再将它们都返回出来
export default connect(({user})=>({...user}))(UserManage);

userModel.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/* eslint-disable comma-dangle */
import { message } from 'antd';
import { getAllUsers } from '../services/userApi';

// 延时函数,返回promise对象
function delay(time) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, time);
  });
}

export default {
  // 命名空间标识符
  namespace: 'user',
  // 全部state,可以定义一些默认值
  state: {
    users: [],
  },
  // 同步处理数据的方法,唯一能够直接改变state的方法,只能用dispatch触发
  // 把新的state retrun出去,用到state数据的界面就会更新,官方推荐处理逻辑都放在effects中
  reducers: {
    // 方法接收2个参数,第一个是旧的state,第二个是action对象,没有可以不写或写_
    setUsers(state, { users }) {
      // 先把旧的state全部解构出来,然后把新的users覆盖原来的,从而实现state的更新
      return { ...state, users };
    },
    deleteUser(state, { id }) {
      const tem = state.users;
      const result = tem.filter(i => id !== i.id);
      message.success('删除成功!');
      return { ...state, users: result };
    }
  },
  // 异步数据处理的方法
  // 推荐数据逻辑处理也应该在此处理,处理完再给reducer
  effects: {
    // 方法接收两个参数,第一个是传过来的action对象(没有可以写 _ ),第二个基本是用其中call, put, select这3个参数(所有的去官网看)
    // call: 用来与后台交互
    // put: 用来触发reducers中的方法,与dispacth功能一样
    // select: 用来选择models层所有命名空间下的state数据,应用于分页查询时获取page和pageSize
    	// const page = yield select((state)=>state.user.page)
    // * yield是es6的Generator函数
    *getAllUsers(_, { call, put }) {
      const resp = yield call(getAllUsers);
      yield put({
        type: 'setUsers',// 这里可以省略命名空间
        users: resp.content
      });
      message.success('查询成功');
    },
    *delayRemoveUser({ id }, { call, put }) {
      yield call(delay, 1000);
      yield put({
        type: 'deleteUser',
        id
      });
    }
  },
  // 订阅监听,可以监听路由,键盘输入等,常用作进入某页面发个请求获取数据,展示出来
  subscriptions: {
    setup({
      dispatch, history, query, store,
    }) {
      return history.listen(({ pathname, search }) => {
        if (pathname === '/userPage') {
          // 监听进入testPage页时,做些操作
          // dispatch({ type: "shoppingWZ" })
        }
      });
    },
  },
};