Umi学习记录(二)

1,710 阅读4分钟

一、在umi中使用dva

umi中的状态管理都是交给dva来做的。

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

dva的整合可以直接通过 umi-plugin-react 来配置,首先在 .umirc.js 里配置dva插件。

export default {
  plugins: [
    [
      'umi-plugin-react',
      {
        dva: true,
      },
    ]
  ],
};

二、Model的概念

dva他将每个页面的数据通过model的概念保存起来。

export default {
  // 命名空间
  namespace: '', 
  // 初始值
  state: {},
  // 以 key/value 格式定义 reducer。
  reducers: {},
  // 以 key/value 格式定义 effect。
  effects: {},
  // subscription 是订阅。
  subscriptions: {},
};

namespace

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

state

状态初始值,也就是数据初始值。

reducers

用于处理同步操作,唯一可以修改 state 的地方。由 action 触发。

effects

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

subscriptions

用于订阅一个数据源,然后根据需要 dispatch 相应的 action。比如:我们可以在这里监听路由,根据路由变化做一些操作。

三、model的注册

model 分两类,一是全局 model,二是页面 model。全局 model 存于 /src/models/ 目录,所有页面都可引用;页面 model 不能被其他页面所引用。

规则如下:

  • src/models/**/*.js 为 global model
  • src/pages/**/models/**/*.js 为 page model
  • global model 全量载入,page model 在 production 时按需载入,在 development 时全量载入
  • page model 为 page js 所在路径下 models/**/*.js 的文件
  • page model 会向上查找,比如 page js 为 pages/a/b.js,他的 page model 为 pages/a/b/models/**/*.js + pages/a/models/**/*.js,依次类推
  • 约定 model.js 为单文件 model,解决只有一个 model 时不需要建 models 目录的问题,有 model.js 则不去找 models/**/*.js

四、例子说明

如果能理解Model中的每个属性,也就能熟练的使用dva了,我们通过下面的一个例子来熟悉dva的使用。

我们在pages下新建如下目录,我们要做的是使用接口请求拿到 todolist 列表数据,然后在todo页面中渲染出来。

+ pages
  + todo
    + models
			- todo.js 
    + services
			- todo.js
  - index.js
  - index.less
页面如何使用model的状态数据

umi 会按照约定将页面对应的 model 文件 来注册 model,以下是todo文件夹下的model

import * as services from '../services/todo';

export default {
  namespace: 'todo', 
  state: {
    todoList: [],
  }, 
  reducers: {
    // 更新数据
    save(state, { payload }) {
      return { ...state, ...payload };
    },
  }, 
  effects: {
    // 采用了 generator 函数
    *getTodoList({ payload }, { call, put, select }) {
      const data = yield call(services.getTodoList);
      yield put({
        type: 'save',
        payload: {
          todoList: data,
        },
      });
    },
  }, 
  // 订阅数据源
  subscriptions: {},
};

那么我们注册成功后的todo页面如何使用models中的数据,也就是dva管理的状态数据。其实和react-redux是一样的,先在pages/todo/index.js 中引入 dvaconnect 方法,是不是很熟悉了,然后使用 connect 方法关联组件。这个时候我们就能拿到对应model的数据了。

import styles from './index.less';
// 引入 connect 方法
import { connect } from 'dva';
import { List } from 'antd';
import { useEffect } from 'react';

function TodoList(props) {
  const { todoList, getTodoList } = props;
  useEffect(() => {
    getTodoList();
  });

  const deleteTodo = () => {
    console.log('deleteTodo');
  };

  return (
    <div className={styles.container}>
      <List
        dataSource={todoList}
        renderItem={item => (
          <List.Item
            actions={[
              <span key="list-loadmore-edit" onClick={deleteTodo}>
                delete
              </span>,
            ]}
          >
            {item.title}
          </List.Item>
        )}
      />
    </div>
  );
}

function mapStateToProps(state) {
  // 这里的 state 是 dva 管理的所有 model的集合。 我们这个页面只需要使用 todo 这个model中的数据。
  return state.todo;
}

function mapDispatchToProps(dispatch) {
  return {
    getTodoList() {
      dispatch({ type: 'todo/getTodoList' });
    },
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps,
)(TodoList);
尝试获取后台数据

首先在Mock中模拟一个接口。我们将todo页面的接口统一放在该页面对应的services/todo.js中。

export function getTodoList() {
  return fetch('http://localhost:8000/api/todolist').then(res => res.json());
}

model 中的 effects 定义 getTodoList 函数。

effects: {
   *getTodoList({ payload }, { call, put, select }) {
     
     // payload 调用getTodoList函数时候传递的参数
     // call 可以理解成 call 方法,执行某个函数。我们下面执行了services.getTodoList方法,有参数的时候需要逗号隔开参数。
     // put 调用 reducers 中的方法更新状态数据,下面就调用了reducers中的save方法,目的是更新state中的todoList数组。
     // select 这个方法是获取当前状态的数据。
     
     // select使用示例。
     const state = yield select(state => state.todo);
     // 上面的 state 就是当前最新的todo的状态数据
     
     const data = yield call(services.getTodoList);
     yield put({
       type: 'save',
       payload: {
         todoList: data,
       },
     });
   },
}

注意:上面的所有异步操作前面都应加上yield 关键字。

我们就可以通过上面 effects 中的 getTodoList 方法获取后台数据了, 首先在 todo 页面定义 getTodoList 函数。然后在组件生命周期中调用函数获取 todoList

// todo 页面定义 getTodoList 函数
function mapDispatchToProps(dispatch) {
  return {
    getTodoList() {
      // 在外面调用model中的方法时,需要加上model的命名空间,如下。
      dispatch({ type: 'todo/getTodoList' });
    },
  };
}

五、总结

umi极大的减少了我们开发的时间成本,路由,状态数据管理等重要的模块框架都给了我们很好并且很方便的解决方案。