初始化
安裝 dva-cli 用于初始化項目:
npm install -g dva-cli
# 或
yarn global add dva-cli
創(chuàng)建項目目錄,并進(jìn)入該目錄:
mkdir your-project
cd your-project
初始化項目:
dva init
然后運(yùn)行 npm start 或 yarn start 即可運(yùn)行項目。
目錄結(jié)構(gòu)
項目初始化以后,默認(rèn)的目錄結(jié)構(gòu)如下:
|- 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 數(shù)據(jù)的文件;
- public 一般用于存放靜態(tài)文件,打包時會被直接復(fù)制到輸出目錄(./dist);
- src 文件夾用于存放項目源代碼;
- asserts 用于存放靜態(tài)資源,打包時會經(jīng)過 webpack 處理;
- components 用于存放 React 組件,一般是該項目公用的無狀態(tài)組件;
- models 用于存放模型文件
- routes 用于存放需要 connect model 的路由組件;
- services 用于存放服務(wù)文件,一般是網(wǎng)絡(luò)請求等;
- utils 工具類庫
- router.js 路由文件
- index.js 項目的入口文件
- index.css 一般是共用的樣式
- .editorconfig 編輯器配置文件
- .eslintrc ESLint配置文件
- .gitignore Git忽略文件
- .roadhogrc.mock.js Mock配置文件
- .webpackrc 自定義的webpack配置文件,JSON格式,如果需要 JS 格式,可修改為 .webpackrc.js
antd 按需引入
先安裝 antd 和 babel-plugin-import:
npm install antd babel-plugin-import --save
# 或
yarn add antd babel-plugin-import
babel-plugin-import 也可以通過 -D 參數(shù)安裝到 devDependencies 中,它用于實現(xiàn)按需加載。
然后在 .webpackrc 中添加如下配置:
{
"extraBabelPlugins": [
["import", {
"libraryName": "antd",
"libraryDirectory": "es",
"style": true
}]
]
}
現(xiàn)在就可以按需引入 antd 的組件了,如 import { Button } from 'antd',Button 組件的樣式文件也會自動幫你引入。
更多 .webpackrc 的配置請參考 roadhog 配置。
自定義 antd 主題
可以在 .webpackrc 中添加 theme 字段直接進(jìn)行主題自定義,但是如果自定義的變量太多,建議將其單獨提取取來方便管理。
建議在 ./src 目錄下新建名為 theme.js 的文件,然后在 .webpackrc 中引入,如下:
{
"theme": "./src/theme.js"
}
theme.js 示例如下:
export default {
"primary-color": "#000",
}
更多可自定義的 antd 變量請參考 default.less。
CSS Modules
使用 dva-cli 初始化的項目默認(rèn)已經(jīng)啟用了 CSS Modules,如果不想使用 CSS Modules,在 .webpackrc 中添加以下配置項即可禁用:
{
"disableCSSModules": true
}
開發(fā)代理
如需開發(fā)過程中代理 API 接口,在 .webpackrc 中添加如下配置即可:
{
"proxy": {
"/api": {
"target": "http://your-api-server",
"changeOrigin": true
}
}
}
Mock
如需 mock 功能,在 .roadhogrc.mock.js 中添加配置即可,如:
export default {
'GET /api/users': { users: [{ username: 'admin' }] },
}
如上配置,當(dāng)請求 /api/users 時會返回 JSON 格式的數(shù)據(jù)。
同時也支持自定義函數(shù),如下:
export default {
'POST /api/users': (req, res) => { res.end('OK'); },
}
具體的 API 請參考 Express.js@4。
當(dāng) mock 數(shù)據(jù)太多時,可以拆分后放到 ./mock 文件夾中,然后在 .roadhogrc.mock.js 中引入。
HMR
HMR,即模塊熱替換,在修改代碼后不需要刷新整個頁面,方便開發(fā)時的調(diào)試??梢栽?.webpackrc 中添加如下配置以使用 HMR:
{
"env": {
"development": {
"extraBabelPlugins": [
"dva-hmr"
]
}
}
}
如果無效,請嘗試更新一下 babel-plugin-dva-hmr。
env 字段是針對特定環(huán)境進(jìn)行配置,因為 HMR 只在開發(fā)環(huán)境下使用,所以將配置添加到 development 字段即可,運(yùn)行 npm run build 時的環(huán)境變量為 production。
組件動態(tài)加載
dva 內(nèi)置了 dynamic 方法用于實現(xiàn)組件的動態(tài)加載,用法如下:
import dynamic from 'dva/dynamic';
const UserPageComponent = dynamic({
app,
models: () => [
import('./models/users'),
],
component: () => import('./routes/UserPage'),
});
實際使用時,可以對其進(jìn)行簡單的封裝,否則每個路由組件都這么寫一遍很麻煩。
dva-loading
dva-loading 是一個用于處理 loading 狀態(tài)的 dva 插件,基于 dva 的管理 effects 執(zhí)行的 hook 實現(xiàn),它會在 state 中添加一個 loading 字段(該字段可自定義),自動幫你處理網(wǎng)絡(luò)請求的狀態(tài),不需要自己再去寫 showLoading 和 hideLoading 方法。
在 ./src/index.js 中引入使用即可:
import createLoading from 'dva-loading';
const app = dva();
app.use(createLoading(opts));
opts 僅有一個 namespace 字段,默認(rèn)為 loading。
Model
Model 是 dva 最重要的部分,可以理解為 redux、react-redux、redux-saga 的封裝。
通常項目中一個模塊對應(yīng)一個 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 上的一個屬性,只能是字符串,不支持使用 . 創(chuàng)建多層命名空間。
state 是狀態(tài)的初始值。
reducer 類似于 redux 中的 reducer,它是一個純函數(shù),用于處理同步操作,是唯一可以修改 state 的地方,由 action 觸發(fā),它有 state 和 action 兩個參數(shù)。
effects 用于處理異步操作,不能直接修改 state,由 action 觸發(fā),也可觸發(fā) action。它只能是 generator 函數(shù),并且有 action 和 effects 兩個參數(shù)。第二個參數(shù) effects 包含 put、call 和 select 三個字段,put 用于觸發(fā) action,call 用于調(diào)用異步處理邏輯,select 用于從 state 中獲取數(shù)據(jù)。
subscriptions 用于訂閱某些數(shù)據(jù)源,并根據(jù)情況 dispatch 某些 action,格式為 ({ dispatch, history }, done) => unlistenFunction。
如上的一個 model,監(jiān)聽路由變化,當(dāng)進(jìn)入 /user 頁面時,執(zhí)行 effects 中的 fetch,以從服務(wù)端獲取用戶列表,然后 fetch 中觸發(fā) reducers 中的 save 將從服務(wù)端獲取到的數(shù)據(jù)保存到 state 中。
注意,在 model 中觸發(fā)這個 model 中的 action 時不需要寫命名空間,比如在 fetch 中觸發(fā) save 時是 { type: 'save' }。而在組件中觸發(fā) action 時就需要帶上命名空間了,比如在某個組件中觸發(fā) fetch 時,應(yīng)該是 { type: 'user/fetch' }。
app
在 ./src/index.js 中可以看到如下代碼:
import dva from 'dva';
const app = dva();
app 就是 dva 實例,創(chuàng)建實例時 dva 方法可以傳入一些參數(shù),如下:
- history
- initialState
- onError
- onAction
- onStateChange
- onReducer
- onEffect
- onHmr
- extraReducers
- extraEnhancers
history 是給路由用的,默認(rèn)為 hashHistory,如果想要使用 browserHistory,需要安裝 history,然后在 ./src/index.js 引入使用:
import dva from 'dva';
import createHistory from 'history/createBrowserHistory';
const app = dva({
history: createHistory(),
});
initialState 是 state 的初始數(shù)據(jù),優(yōu)先級高于 model 中的 state,默認(rèn)為 {}。
其他以 on 開頭的均為鉤子函數(shù),更多請參考 dva API。
connect
當(dāng)寫完 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 拋出的錯誤都會經(jīng)過 onError 鉤子函數(shù),所以可以在 onError 中進(jìn)行全局錯誤處理。
const app = dva({
onError(err, dispatch) {
console.error(err);
},
});
如果需要對某些 effects 進(jìn)行特殊的錯誤處理,可以使用 try catch。
異步請求
dva 集成了 isomorphic-fetch 用于處理異步請求,并且使用 dva-cli 初始化的項目中,已經(jīng)在 ./src/utils/request.js 中對 fetch 進(jìn)行了簡單的封裝,可以在這里根據(jù)服務(wù)端 API 的數(shù)據(jù)結(jié)構(gòu)進(jìn)行統(tǒng)一的錯誤處理。
當(dāng)然如果你不想用 fetch,完全可以引入自己喜歡的第三方庫,沒有任何影響,打包時也不會將 isomorphic-fetch 打包進(jìn)去。