dva 的基本用法

编辑于2018年07月20日

dva 是基于 redux、redux-saga 和 react-router 的轻量级前端框架,概念来自 elm,支持 side effects、热替换、动态加载、react-native、SSR 等,已在生产环境广泛应用。

最早了解到 dva 是在使用蚂蚁金服出品的 antd 组件库时,官方文档上介绍了如何使用 dva 和 antd 创建一个应用。不过当时并没有尝试使用 dav,而是在 create-react-app 中使用了 antd 组件库。

年前克隆了 ant-design-pro 的代码,想要学习一下它的应用架构。ant-design-pro 基于 ES2015+、React、dva、g2 和 antd,虽说学习应用架构并不是特别需要了解底层技术,但却激发我对 dva 的学习欲望。

在学习过程中,我首先阅读了 README 中提及到了基本概念和 API,然后跟着教程做了一个 demo。关于 dva 的基本概念、API 等这里就不介绍了,看官方的文档会更好,我主要记录下我在跟着教程完成 demo 中遇到的问题和个人的总结。

预备知识:redux-saga

dva 基于 redux、redux-saga 和 react-router,redux 和 react-router 我有一定的了解,但是之前 redux 异步操作都是通过 redux-thunk 来完成,因此对于 redux-sage 不是很清楚,所以先花了一些时间简单学习一下 redux-saga。

redux-saga 是一个用于管理 Redux 应用异步操作(又称异步 action)的中间件。 redux-saga 通过创建 Sagas 将所有的异步操作逻辑收集在一个地方集中处理,可以用来代替 redux-thunk 中间件。其中,Sagas 是通过 Generator 函数来创建的,它监听发起的 action,然后决定基于这个 action 来做什么:是发起一个异步调用(比如一个 Ajax 请求),还是发起其他的 action 到 Store,甚至是调用其他的 Sagas。

在具体用法上,我理解 redux-saga 主要使用四个 API

  • takeEvery:监听 action,并调用对应的函数;
  • takeLast:当有多个 action 触发时,只处理最后一个;
  • call:执行异步操作;
  • put:发起 action。

一个比较完成的 saga.js 如下:

import { takeEvery, takeLatest } from 'redux-saga'
import { call, put } from 'redux-saga/effects'
import Api from '...'

function* fetchUser(action) {
   try {
      const user = yield call(Api.fetchUser, action.payload.userId);
      yield put({type: "USER_FETCH_SUCCEEDED", user: user});
   } catch (e) {
      yield put({type: "USER_FETCH_FAILED", message: e.message});
   }
}

function* mySaga() {
  yield* takeLatest("USER_FETCH_REQUESTED", fetchUser);
}

更详细的介绍可以参考此文档

初始化应用

dva 为我们提供了命令行工具,可以方便的构建应用,我使用的是最新版的 dva2.1.0 和 antd3.2.0。

首先通过 npm i dva-cli -g 命令全局安装 dva 命令行工具,该工具提供了三个命令:

  • init:在当前文件夹下初始化应用。
  • new:创建一个新的应用。
  • generate(简写 g):生成代码,如:model、route。不过由于时间原因,当前版本(0.9.2)并不支持。

命令行工具安装完成后,直接使用 dva new <project-name> 创建应用,应用的目录结构如下所示:

.
├── mock                # 模拟的数据文件
├── node_modules        # 项目依赖包(执行 npm install 下载 )
├── public              # 公共资源目录,构建时会被自动复制到输出目录
│   └── index.html      # 入口 html
├── src                 # 源码目录
│   ├── assets          # 静态资源
│   ├── components      # 组件
│   ├── models          # 数据模型
│   ├── routes          # 路由页面
│   ├── services        # 服务,主要供 models 使用,提供一些 API 等
│   ├── utils           # 存放各种工具方法,如:封装的 request 模块
│   ├── index.css       # 项目主 css
│   ├── index.js        # 入口文件
│   └── router.js       # 路由
├── .editorconfig       # 编辑器配置
├── .eslintrc           # eslint 配置文件
├── .gitignore          # 配置 git 忽略的目录和文件
├── .roadhogrc.mock.js  # mock 数据配置
├── .webpackrc          # webpack 配置
└── package.json        # 项目配置文件

使用命令行工具创建完项目之后,最好先了解一下他的 配置方式

接着我们配置一下 antd 和 babel-plugin-import,其中 babel-plugin-import 用于按需引入 antd 的 JavaScript 和 CSS,这样打包出来的文件不至于太大。执行下面两个命令安装:

$ npm i -S antd
$ npm i -D babel-plugin-import

安装完成后编辑 .webpackrc,添加如下配置:

{
  "extraBabelPlugins": [
    [
      "import",
      {
        "libraryName": "antd",
        "libraryDirectory": "es",
        "style": "css"
      }
    ]
  ]
}

最后,我们配置一下代理,能通过 RESTFul 的方式访问 http://localhost:8000/api/users ,在 .webpackrc 里加上如下配置:

{
  "proxy": {
    "/api": {
      "target": "http://jsonplaceholder.typicode.com/",
      "changeOrigin": true,
      "pathRewrite": {
        "^/api": ""
      }
    }
  }
}

完成了上述的工作后,我们就可以通过 npm start 命令启动应用。访问 http://localhost:8000/api/users ,就能访问到 http://jsonplaceholder.typicode.com/users 的数据。

项目开发

完成了项目创建和一些配置工作后,就可以正式进行项目开发了。开发过程大致可以分为四个步骤

  1. 添加路由:可以通过 dva g route <route-name> 生成(目前不支持),这一命令完成了在 routes 文件下创建对应的路由页面和 css 文件以及在 routes.js 添加页面的配置的工作。
  2. 添加 model:可以通过 dva g model <model-name> 生成(目前不支持)。
  3. 添加 service:在 services 目录下添加对应 model 所需要的服务,例如:请求数据。
  4. 添加界面:可以通过 dva g component <path/name> 生成组件(目前不支持)。

路由、service 以及组件的编写这里就不介绍了,就是正常的 React 应用开发模式,这里主要说明一下 model,一个 model 文件大概长这样:

export default {
  namespace: 'example',
  state: {},
  subscriptions: {
    setup ({dispatch, history}) {
    }
  },
  effects: {
    * fetch ({payload}, {call, put}) {
      yield put({type: 'save'})
    },
    takeLatest: [function * ({payload}, {call, put}) {
    }, {type: 'takeLatest'}]
  },
  reducers: {
    save (state, action) {
      return {...state, ...action.payload}
    }
  }
}

可以看到,model 包含了五个属性,各个属性的含义如下:

  • namespace:命名空间,同时也是他在全局 state 上的属性名。
  • state:初始 state。
  • subscriptions:订阅数据源,然后根据需要 dispatch 相应的 action。
  • effects:用于处理异步操作和业务逻辑,不直接修改 state。可以指定 type,写法参考上文 takeLatest
  • reducers:同 redux 中的 redur 一样,用于处理同步操作,唯一可以修改 state 的地方。

model 创建好后,需要在 src/index.js 中注册,如下所示:

app.model(require('./models/example').default);

插件

dva 有一个管理 effects 执行的 hook,并基于此封装了 dva-loading 插件。通过这个插件,我们可以不必一遍遍地写 showLoading 和 hideLoading,当发起请求时,插件会自动设置数据里的 loading 状态为 true 或 false 。然后我们在渲染 components 时绑定并根据这个数据进行渲染。

先安装 dva-loading :

$ npm i -S dva-loading

修改 src/index.js 加载插件,在合适的地方加入下面两句:

+ import createLoading from 'dva-loading';
+ app.use(createLoading());

然后在组件中里绑定 loading 数据:

+ loading: state.loading.models.users

这样当发起请求时,组件 loading 属性就会发生相应的变化。除此之外,dva 还包含其他的 hooks,可以用来实现相应的功能。

总结

dva 确实如官网介绍所说,十分的轻量、易学易用,花 20 分钟不到的时间就能入门。它只是基于现有的 React 应用架构进行了封装,没有引入什么新的概念。它明确的说明了每个部件应该如何写,减少了以必要的纠结。同时,提供了 app.model 方法,把 reducer, initialState, action, saga 封装到了一起,避免我们在多个文件中来回切换。

在学习 dva 的基本用法的过程中,我还了解到了一些其他知识:

希望对您能有帮助,打赏随意