前置知識
umi + dva,完成使用者管理的 CURD 應用
本文主要是在 dva 作者 @sorrycc 的例子 umi + dva,完成使用者管理的 CURD 應用 基礎上進行的一些自己的學習記錄。 關於評論區的小夥伴說的照抄 @sorrycc 大神的github文章的問題,這裡迴應下,沒錯,本文 90% 以上的文字都出自 umi + dva,完成使用者管理的 CURD 應用 這篇文章,而且作者和文章出處一開始就交代清楚了,寫這個文章的目的是記錄一下自己跟著教程 step by step 完成這個應用的過程,並不是教程,期望教程的小夥伴, @sorrycc 大大在 dva 官網上提供了很多豐富的例子,可以參考。
2018年9月10日更新,umi 已經升級到了 2.0 版本,希望使用 umi@2 開發的同學請移步至 DvaJS的學習之路2 - umi@2 + dva,完成使用者管理的 CURD 應用。
開始之前:
- 確保 node 版本是 8.4 或以上
- 用 cnpm 或 yarn 能節約你安裝依賴的時間
Step 1. 安裝 dva-cli@next 並建立應用
先安裝 dva-cli,並確保版本是 1.0.0-beta.2 或以上。
$ cnpm i dva-cli@next -g
$ dva -v
dva-cli version 1.0.0-beta.4
dva version 2.3.1
複製程式碼
這裡需要注意的是安裝dva-cli@next版本的原因是目前 umi 還未提供官方的腳手架工具,需要 dva + umi 結合使用可以使用 dva-cli@next 方式來初始化專案。詳見:例子和腳手架。
然後建立應用:
$ dva new user-dashboard
$ cd user-dashboard
複製程式碼
Step 2. 配置代理,能通過 RESTFul 的方式訪問 http://localhost:8000/api/users
修改 .umirc.js
,加上 "proxy" 配置:
proxy: {
"/api": {
"target": "http://jsonplaceholder.typicode.com/",
"changeOrigin": true,
"pathRewrite": { "^/api" : "" }
}
},
複製程式碼
然後啟動應用:(這個命令一直開著,後面不需要重啟)
$ npm start
複製程式碼
瀏覽器會自動開啟,並開啟 http://localhost:8000。
訪問 http://localhost:8000/api/users ,就能訪問到 jsonplaceholder.typicode.com/users 的資料。(由於 typicode.com 服務的穩定性,偶爾可能會失敗。不過沒關係,正好便於我們之後對於出錯的處理)
Step 3. 生成 users 路由
umi 中檔案即路由,所以我們要新增路由,新建檔案即可,詳見 umijs.org/guide/route… 。
新建 src/pages/users/page.js
,內容如下:
export default () => {
return (
<div>
Users Page
</div>
)
}
複製程式碼
然後訪問 http://localhost:8000/users ,你會看到 Users Page
的輸出。
注意:使用 umi 約定
src/pages
目錄下的檔案即路由,而檔案則匯出 react 元件。可以看到 umi 的特點:以頁面維度,將 models 、 services 組織到一起。
Step 4. 構造 users model 和 service
新增 service: src/pages/users/services/users.js
:
import request from '../../../utils/request';
export function fetch({ page = 1 }) {
return request(`/api/users?_page=${page}&_limit=5`);
}
複製程式碼
注意這裡的
page
引數預設為1
,limit
引數設定為5
新增 model: src/pages/users/models/users.js
,內容如下:
import * as usersService from '../services/users';
export default {
namespace: 'users',
state: {
list: [],
total: null,
},
reducers: {
save(state, { payload: { data: list, total } }) {
return { ...state, list, total };
},
},
effects: {
*fetch({ payload: { page } }, { call, put }) {
const { data, headers } = yield call(usersService.fetch, { page });
yield put({ type: 'save', payload: { data, total: headers['x-total-count'] } });
},
},
subscriptions: {
setup({ dispatch, history }) {
return history.listen(({ pathname, query }) => {
if (pathname === '/users') {
dispatch({ type: 'fetch', payload: query });
}
});
},
},
};
複製程式碼
這裡面有一些寫法之前沒有用過:比如
{ payload: { data: list, total } }
, 這個是析構時配 alias 的寫法;return { ...state, list, total }
的寫法用了Spread Operator...
來組合新物件, 詳見 dva知識地圖#ES6物件和陣列
由於我們需要從 response headers 中獲取 total users 數量,所以需要改造下 src/utils/request.js
:
import fetch from 'dva/fetch';
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
/**
* Requests a URL, returning a promise.
*
* @param {string} url The URL we want to request
* @param {object} [options] The options we want to pass to "fetch"
* @return {object} An object containing either "data" or "err"
*/
export default async function request(url, options) {
const response = await fetch(url, options);
checkStatus(response);
const data = await response.json();
const ret = {
data,
headers: {},
};
if (response.headers.get('x-total-count')) {
ret.headers['x-total-count'] = response.headers.get('x-total-count');
}
return ret;
}
複製程式碼
注意:這裡使用了 ES7 的 async/await 特性,開始對這塊不是很熟悉,看了一些關於 async/await 的文章,發現確實比 Promise 的寫法語義化更加明顯,下面這段是我改寫的 Promise 寫法:
/**
* Requests a URL, returning a promise.
*
* @param {string} url The URL we want to request
* @param {object} [options] The options we want to pass to "fetch"
* @return {object} An object containing either "data" or "err"
*/
export default function request(url, options) {
let headers = {}
return fetch(url, options)
.then(checkStatus)
.then(response => {
const data = parseJSON(response)
if (response.headers.get('x-total-count')) {
headers['x-total-count'] = response.headers.get('x-total-count');
}
return data;
}).then((data) => {
return {data, headers}
}).catch(err => ({ err }));
}
複製程式碼
切換到瀏覽器(會自動重新整理),應該沒任何變化,因為資料雖然好了,但並沒有檢視與之關聯。但是開啟 Redux 開發者工具,應該可以看到 users/fetch
和 users/save
的 action 以及相關的 state 。
Step 5. 新增介面,讓使用者列表展現出來
我們把元件存在 src/pages/users/components
裡,所以在這裡新建 Users.js
和 Users.css
。具體參考這個 Commit。
需留意兩件事:
- 對 model 進行了微調,加入了 page 表示當前頁
- 由於 components 和 services 中都用到了 pageSize,所以提取到
src/constants.js
改完後,切換到瀏覽器,應該能看到帶分頁的使用者列表。
有幾點需要注意:
- Users.js 裡面使用了 antd 的元件,但是專案並沒有手動安裝 antd, 原來是 umi 幫我們引入了 antd 。
- Users.js 裡面將model和元件連線了起來,注意
const { list, total, page } = state.users;
裡面的users
為model
裡面的namespace
名稱。- 我們沒有手動註冊 model,umi 幫我們進行了這一步操作, 詳見
src/pages/.umi/DvaContainer.js
檔案,該檔案會自動更新。相關規則詳見 umi官網#model註冊 一節。- 可以直接使用 css module
Step 6. 新增 layout
新增 layout 佈局,使得我們可以在首頁和使用者列表頁之間來回切換。umi 里約定 layouts/index.js
為全域性路由,所以我們新增 src/layouts/index.js
和 CSS 檔案即可。
參考這個 Commit。
注意:
頁頭的選單會隨著頁面切換變化,高亮顯示當前頁所在的選單項
Step 7. 處理 loading 狀態
dva 有一個管理 effects 執行的 hook,並基於此封裝了 dva-loading 外掛。通過這個外掛,我們可以不必一遍遍地寫 showLoading 和 hideLoading,當發起請求時,外掛會自動設定資料裡的 loading 狀態為 true 或 false 。然後我們在渲染 components 時繫結並根據這個資料進行渲染。
umi-plugin-dva 預設內建了 dva-loading 外掛。
然後在 src/components/Users/Users.js
裡繫結 loading 資料:
+ loading: state.loading.models.users,
複製程式碼
具體參考這個 Commit 。
重新整理瀏覽器,你的使用者列表有 loading 了沒?
Step 8. 處理分頁
只改一個檔案 src/pages/users/components/Users.js
就好。
處理分頁有兩個思路:
- 發 action,請求新的分頁資料,儲存到 model,然後自動更新頁面
- 切換路由 (由於之前監聽了路由變化,所以後續的事情會自動處理)
我們用的是思路 2 的方式,好處是使用者可以直接訪問到 page 2 或其他頁面。
參考這個 Commit 。
Step 9. 處理使用者刪除
經過前面的 8 步,應用的整體脈絡已經清晰,相信大家已經對整體流程也有了一定了解。
後面的功能調整基本都可以按照以下三步進行:
-
service
-
model
-
component 我們現在開始增加使用者刪除功能。
-
service, 修改
src/pages/users/services/users.js
:
export function remove(id) {
return request(`/api/users/${id}`, {
method: 'DELETE',
});
}
複製程式碼
- model, 修改
src/pages/users/models/users.js
:
*remove({ payload: id }, { call, put, select }) {
yield call(usersService.remove, id);
const page = yield select(state => state.users.page);
yield put({ type: 'fetch', payload: { page } });
},
複製程式碼
- component, 修改
src/pages/users/components/Users.js
,替換deleteHandler
內容:
dispatch({
type: 'users/remove',
payload: id,
});
複製程式碼
切換到瀏覽器,刪除功能應該已經生效。
Step 10. 處理使用者編輯
處理使用者編輯和前面的一樣,遵循三步走:
- service
- model
- component
先是 service,修改
src/pages/users/services/users.js
:
export function patch(id, values) {
return request(`/api/users/${id}`, {
method: 'PATCH',
body: JSON.stringify(values),
});
}
複製程式碼
再是 model,修改 src/pages/users/models/users.js
:
*patch({ payload: { id, values } }, { call, put, select }) {
yield call(usersService.patch, id, values);
const page = yield select(state => state.users.page);
yield put({ type: 'fetch', payload: { page } });
},
複製程式碼
最後是 component,詳見 Commit。
需要注意的一點是,我們在這裡如何處理 Modal 的 visible 狀態,有幾種選擇:
- 存 dva 的 model state 裡
- 存 component state 裡
另外,怎麼存也是個問題,可以:
- 只有一個 visible,然後根據使用者點選的 user 填不同的表單資料
- 幾個 user 幾個 visible
此教程選的方案是 2-2,即存 component state,並且 visible 按 user 存。另外為了使用的簡便,封裝了一個
UserModal
的元件。
完成後,切換到瀏覽器,應該就能對使用者進行編輯了。
Step 11. 處理使用者建立
相比使用者編輯,使用者建立更簡單些,因為可以共用 UserModal 元件。和 Step 10 比較類似,就不累述了,詳見 Commit 。
到這裡,我們已經完成了一個完整的 CURD 應用。如果感興趣,可以進一步看下 dva 和 umi 的資料:
(完)
總結
做這個練習主要了解了 dva 和 umi 搭配使用的方式,使用 umi 的一些寫法和 umi 的特點。感覺學習了Redux之後,dva上手會很快,dva 的網站資源很豐富,希望和大家一起學習,後續還會有 dva 學習的文章。