林秀栋的技术博客

dva安装和使用

dva 是什么

截止 2017.1,最流行的社区 React 应用架构方案如下。

缺点:要引入多个库,项目结构复杂。

dva 是体验技术部开发的 React 应用框架,将上面三个 React 工具库包装在一起,简化了 API,让开发 React 应用更加方便和快捷。

dva = React-Router + Redux + Redux-saga

通过dva-cli初始化项目

安装dva-cli

npm install dva-cli -g

创建新应用

dva new 项目名

cd进去

npm run start

安装ant-design

通过 npm 安装 antd 和 babel-plugin-import。babel-plugin-import 是用来按需加载 antd 的脚本和样式的。

npm install antd babel-plugin-import –save

编辑 .webpackrc,使 babel-plugin-import 插件生效。

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

开发代理

如需开发过程中代理 API 接口,在 .webpackrc 中添加如下配置即可:

{
  "proxy": {
    "/api": {
      "target": "http://your-api-server",
      "changeOrigin": true
    }
  }
}

Mock

如需 mock 功能,在 .roadhogrc.mock.js 中添加配置即可,如:

export default { ‘GET /api/users’: { users: [{ username: ‘admin’ }] }, }

如上配置,当请求 /api/users 时会返回 JSON 格式的数据。

同时也支持自定义函数,如下:

export default {
  'POST /api/users': (req, res) => { res.end('OK'); },
}

具体的 API 请参考 Express.js@4。

当 mock 数据太多时,可以拆分后放到 ./mock 文件夹中,然后在 .roadhogrc.mock.js 中引入。

HMR

HMR,即模块热替换,在修改代码后不需要刷新整个页面,方便开发时的调试。可以在 .webpackrc 中添加如下配置以使用 HMR:

{
  "env": {
    "development": {
      "extraBabelPlugins": [
        "dva-hmr"
      ]
    }
  }
}

如果无效,请尝试更新一下 babel-plugin-dva-hmr。

env 字段是针对特定环境进行配置,因为 HMR 只在开发环境下使用,所以将配置添加到 development 字段即可,运行 npm run build 时的环境变量为 production。

组件动态加载

dva 内置了 dynamic 方法用于实现组件的动态加载,用法如下:

import dynamic from 'dva/dynamic';

const UserPageComponent = dynamic({
  app,
  models: () => [
    import('./models/users'),
  ],
  component: () => import('./routes/UserPage'),
});

实际使用时,可以对其进行简单的封装,否则每个路由组件都这么写一遍很麻烦。

dva-loading

dva-loading 是一个用于处理 loading 状态的 dva 插件,基于 dva 的管理 effects 执行的 hook 实现,它会在 state 中添加一个 loading 字段(该字段可自定义),自动帮你处理网络请求的状态,不需要自己再去写 showLoading 和 hideLoading 方法。

在 ./src/index.js 中引入使用即可:

import createLoading from 'dva-loading';
const app = dva();
app.use(createLoading(opts));

opts 仅有一个 namespace 字段,默认为 loading。

Model

Model 是 dva 最重要的部分,可以理解为 redux、react-redux、redux-saga 的封装。

通常项目中一个模块对应一个 model,一个基本的 model 如下:

import { fetchUsers } from '../services/user';

export default {
  namespace: 'user',
  state: {
    list: [],
  },
  reducers: {
    save(state, action) {
      return {
        ...state,
        list: action.data,
      };
    },
  },
  effects: {
    *fetch(action, { put, call }) {
      const users = yield put(fetchUsers, action.data);
      yield put({ type: 'save', data: users });
    },
  },
  subscriptions: {
    setup({ dispatch, history }) {
      return history.listen(({ pathname }) => {
        if (pathname === '/user') {
          dispatch({ type: 'fetch' });
        }
      });
    },
  },
}

如上的一个 model,监听路由变化,当进入 /user 页面时,执行 effects 中的 fetch,以从服务端获取用户列表,然后 fetch 中触发 reducers 中的 save 将从服务端获取到的数据保存到 state 中。

注意,在 model 中触发这个 model 中的 action 时不需要写命名空间,比如在 fetch 中触发 save 时是 { type: ‘save’ }。而在组件中触发 action 时就需要带上命名空间了,比如在某个组件中触发 fetch 时,应该是 { type: ‘user/fetch’ }。

app

在 ./src/index.js 中可以看到如下代码:

import dva from 'dva';
const app = dva();

app 就是 dva 实例,创建实例时 dva 方法可以传入一些参数,如下:

history 是给路由用的,默认为 hashHistory,如果想要使用 browserHistory,需要安装 history,然后在 ./src/index.js 引入使用:

import dva from 'dva';
import createHistory from 'history/createBrowserHistory';
const app = dva({
  history: createHistory(),
});

initialState 是 state 的初始数据,优先级高于 model 中的 state,默认为 {}。

其他以 on 开头的均为钩子函数,更多请参考 dva API。

connect

当写完 model 和组件后,需要将 model 和组件连接起来。dva 提供了 connect 方法,其实它就是 react-redux 的 connect。用法如下:

import React from 'react';
import { connect } from 'dva';

const User = ({ dispatch, user }) => {
  return (
    <div></div>
  )
}

export default connect(({ user }) => {
  return user;
})(User);

connect 后的组件除了可以获取到 dispatch 和 state,还可以获取到 location 和 history。

错误处理

effects 和 subscriptions 抛出的错误都会经过 onError 钩子函数,所以可以在 onError 中进行全局错误处理。

const app = dva({
  onError(err, dispatch) {
    console.error(err);
  },
});

如果需要对某些 effects 进行特殊的错误处理,可以使用 try catch。

异步请求

dva 集成了 isomorphic-fetch 用于处理异步请求,并且使用 dva-cli 初始化的项目中,已经在 ./src/utils/request.js 中对 fetch 进行了简单的封装,可以在这里根据服务端 API 的数据结构进行统一的错误处理。

当然如果你不想用 fetch,完全可以引入自己喜欢的第三方库,没有任何影响,打包时也不会将 isomorphic-fetch 打包进去。

定义路由

我们要写个应用来先显示产品列表。首先第一步是创建路由,路由可以想象成是组成应用的不同页面。

新建 routes/Products.js,内容如下:

import React from 'react';

const Products = (props) => (
  <h2>List of Products</h2>
);

export default Products;

添加路由信息到路由表,编辑 router.js :

import Products from './routes/Products';
//...
<Route path="/products" exact component={Products} />

然后在浏览器里打开 http://localhost:8000/#/products ,你应该能看到前面定义的 <h2> 标签。