dva.js 上手

初始化

安装 dva-cli 用于初始化项目:

npm install -g dva-cli
# 或
yarn global add dva-cli

创建项目目录,并进入该目录:

mkdir your-project
cd your-project

初始化项目:

dva init

然后运行 npm startyarn start 即可运行项目。

目录结构

项目初始化以后,默认的目录结构如下:

|- mock
|- node_modules
|- package.json
|- public
|- src
    |- asserts
    |- components
    |- models
    |- routes
    |- services
    |- utils
    |- router.js
    |- index.js
    |- index.css
|- .editorconfig
|- .eslintrc
|- .gitignore
|- .roadhogrc.mock.js
|- .webpackrc
  • mock 存放用于 mock 数据的文件;
  • public 一般用于存放静态文件,打包时会被直接复制到输出目录(./dist);
  • src 文件夹用于存放项目源代码;
    • asserts 用于存放静态资源,打包时会经过 webpack 处理;
    • components 用于存放 React 组件,一般是该项目公用的无状态组件;
    • models 用于存放模型文件
    • routes 用于存放需要 connect model 的路由组件;
    • services 用于存放服务文件,一般是网络请求等;
    • utils 工具类库
    • router.js 路由文件
    • index.js 项目的入口文件
    • index.css 一般是共用的样式
  • .editorconfig 编辑器配置文件
  • .eslintrc ESLint配置文件
  • .gitignore Git忽略文件
  • .roadhogrc.mock.js Mock配置文件
  • .webpackrc 自定义的webpack配置文件,JSON格式,如果需要 JS 格式,可修改为 .webpackrc.js

antd 按需引入

先安装 antdbabel-plugin-import

npm install antd babel-plugin-import --save
# 或
yarn add antd babel-plugin-import

babel-plugin-import 也可以通过 -D 参数安装到 devDependencies 中,它用于实现按需加载。

然后在 .webpackrc 中添加如下配置:

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

现在就可以按需引入 antd 的组件了,如 import { Button } from 'antd',Button 组件的样式文件也会自动帮你引入。

更多 .webpackrc 的配置请参考 roadhog 配置

自定义 antd 主题

可以在 .webpackrc 中添加 theme 字段直接进行主题自定义,但是如果自定义的变量太多,建议将其单独提取取来方便管理。

建议在 ./src 目录下新建名为 theme.js 的文件,然后在 .webpackrc 中引入,如下:

{
  "theme": "./src/theme.js"
}

theme.js 示例如下:

export default {
  "primary-color": "#000",
}

更多可自定义的 antd 变量请参考 default.less

CSS Modules

使用 dva-cli 初始化的项目默认已经启用了 CSS Modules,如果不想使用 CSS Modules,在 .webpackrc 中添加以下配置项即可禁用:

{
  "disableCSSModules": true
}

开发代理

如需开发过程中代理 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 字段(该字段可自定义),自动帮你处理网络请求的状态,不需要自己再去写 showLoadinghideLoading 方法。

./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' });
        }
      });
    },
  },
}

namespace 是该 model 的命名空间,同时也是全局 state 上的一个属性,只能是字符串,不支持使用 . 创建多层命名空间。

state 是状态的初始值。

reducer 类似于 redux 中的 reducer,它是一个纯函数,用于处理同步操作,是唯一可以修改 state 的地方,由 action 触发,它有 stateaction 两个参数。

effects 用于处理异步操作,不能直接修改 state,由 action 触发,也可触发 action。它只能是 generator 函数,并且有 actioneffects 两个参数。第二个参数 effects 包含 putcallselect 三个字段,put 用于触发 actioncall 用于调用异步处理逻辑,select 用于从 state 中获取数据。

subscriptions 用于订阅某些数据源,并根据情况 dispatch 某些 action,格式为 ({ dispatch, history }, done) => unlistenFunction

如上的一个 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
  • initialState
  • onError
  • onAction
  • onStateChange
  • onReducer
  • onEffect
  • onHmr
  • extraReducers
  • extraEnhancers

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-reduxconnect。用法如下:

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

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

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

connect 后的组件除了可以获取到 dispatchstate,还可以获取到 locationhistory

错误处理

effectssubscriptions 抛出的错误都会经过 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 打包进去。

参考链接

原文地址:https://pengtikui.cn/dva.js-get-started/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342