DvaJS的學習之路2 - umi@2 + dva,完成使用者管理的 CURD 應用

清秋發表於2018-09-08

前言

最近一個月來使用 dva 對公司存量專案進行重構,比較少時間寫文章了。隨著9月開學季節的到來,最近在使用的幾個開源專案都迎來了重大更新。首先就是 umi 終於迎來了 2.0 版本,具體介紹可以檢視 釋出 umi 2.0,可插拔的企業級 react 應用框架。隨之而來的是使用 umi@2 構建的 ant design pro 2.0 版本,具體介紹可以檢視漂亮的實力派 Ant Design Pro 2.0 正式釋出。今天終於有時間體驗了 umi@2 ,想起我的 dva 學習之路的第一篇學習記錄是使用 umi@1.X 構建的,剛好可以使用 umi 2.0 重新構建一下。本文是 dva 作者 @sorryccumi-dva-user-dashboard 的 umi@2 版本實現,是 《umi + dva,完成使用者管理的 CURD 應用》 文章的 umi@2 版本改寫。程式碼倉庫: umi2-dva-user-dashboard。演示地址:demo

Step 1. 使用 create-umi 腳手架初始化專案

詳見 umi官網#通過腳手架建立專案

使用 yarn create 命令:

$ mkdir umi2-dva-user-dashboard && cd umi2-dva-user-dashboard
$ yarn create umi
複製程式碼

看到社群有小夥伴問到如何使用 npm 建立 umi 專案,首先使用 npm 安裝 create-umi :

$ cnpm install -g create-umi
複製程式碼

安裝好之後,進入專案,然後執行 create-umi

$ cd umi2-dva-user-dashboard
$ create-umi
複製程式碼

注意官方推薦使用 yarn create 命令,因為能確保每次使用最新的腳手架。如果你和我一樣,公司是內網開發環境只有 npm 私服,可以嘗試使用 npm 命令。

完成上述操作後,進入 create-umi 互動式命令列,選擇功能,這裡選擇 antd、dva 和 hard source

image

確定後,會根據你的選擇自動建立好目錄和檔案:

image

然後手動安裝依賴:

$ yarn
複製程式碼

$ cnpm install
複製程式碼

安裝好依賴後啟動專案:

$ yarn start
複製程式碼

$ npm start
複製程式碼

如果順利,在瀏覽器開啟 http://localhost:8000 可看到以下介面:

image

Step 2. 配置代理,能通過 RESTFul 的方式訪問 http://localhost:8000/api/users

修改 .umirc.js ,加上 "proxy" 配置:

proxy: {
  "/api": {
    "target": "http://jsonplaceholder.typicode.com/",
    "changeOrigin": true,
    "pathRewrite": { "^/api" : "" }
  }
},
複製程式碼

注意,代理配置與 umi@1 的配置相同。

訪問 http://localhost:8000/api/users ,就能訪問到 jsonplaceholder.typicode.com/users 的資料。

image

Step 3. 生成 users 路由

umi 中檔案即路由,所以我們要新增路由,新建檔案即可,這裡使用約定式路由。詳見 umijs.org/zh/guide/ro…

新建 src/pages/users.js,內容如下:

export default () => {
  return (
    <div>
      Users Page
    </div>
  )
}
複製程式碼

然後訪問 http://localhost:8000/users ,你會看到 Users Page 的輸出:

image

這裡看到頁面多出了一個頭部,原來是 create-umi 腳手架生成的專案會預設生成一個全域性 layout 檔案 src/layouts/index.js, umi@1.X 版本就具有這個特性,詳見官方文件全域性layout

Step 4. 構造 users model 和 service

注意剛才建立的 src/pages/users.js 是簡單的頁面情況,目前需要和 model 與 service 組織到一起, 新建 src/pages/users/index.js 檔案,將 src/pages/users.js 內容複製到 src/pages/users/index.js 檔案中,然後刪除 src/pages/users.js 檔案。

create-umi 腳手架沒有生成 request.js, 新建 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;
}
複製程式碼

新增 service: src/pages/users/services/users.js,注意 umi@2 新增了 webpack alias @,指向 src 目錄:

import request from '@/utils/request';

export function fetch({ page = 1 }) {
  return request(`/api/users?_page=${page}&_limit=5`);
}
複製程式碼

注意這裡的 page 引數預設為 1limit 引數設定為 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 });
        }
      });
    },
  },
};
複製程式碼

切換到瀏覽器(會自動重新整理),應該沒任何變化,因為資料雖然好了,但並沒有檢視與之關聯。但是開啟 Redux 開發者工具,應該可以看到 users/fetchusers/save 的 action 以及相關的 state 。

image

Step 5. 新增介面,讓使用者列表展現出來(與umi@1相同)

我們把元件存在 src/pages/users/components 裡,所以在這裡新建 Users.jsUsers.css。具體參考這個 Commit

需留意兩件事:

  1. 對 model 進行了微調,加入了 page 表示當前頁
  2. 由於 components 和 services 中都用到了 pageSize,所以提取到 src/constants.js

改完後,切換到瀏覽器,應該能看到帶分頁的使用者列表。

image

有幾點需要注意:

  • Users.js 裡面將 model 和元件連線了起來,注意 const { list, total, page } = state.users; 裡面的 usersmodel 裡面的 namespace 名稱。
  • 我們沒有手動註冊 model,umi 幫我們進行了這一步操作, 詳見 src/pages/.umi/DvaContainer.js 檔案,該檔案會自動更新。相關規則詳見 umi官網#model註冊 一節。
  • 可以直接使用 css module

Step 6. 新增 Header Menu

新增頭部選單元件,使得我們可以在首頁和使用者列表頁之間來回切換。

參考這個 Commit

image

Step 7. 處理 loading 狀態(與umi@1相同)

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,
複製程式碼

注意這裡的 usersmodelnamespace, 所以 dva-loading 的 loading 狀態是對於 model 整體的。

具體參考這個 Commit

重新整理瀏覽器,你的使用者列表有 loading 了沒?

Step 8. 處理分頁(與umi@1相同)

只改一個檔案 src/pages/users/components/Users.js 就好。

處理分頁有兩個思路:

  1. 發 action,請求新的分頁資料,儲存到 model,然後自動更新頁面
  2. 切換路由 (由於之前監聽了路由變化,所以後續的事情會自動處理)

我們用的是思路 2 的方式,好處是使用者可以直接訪問到 page 2 或其他頁面。

quan參考這個 Commit

Step 9. 處理使用者刪除(與umi@1相同)

經過前面的 8 步,應用的整體脈絡已經清晰,相信大家已經對整體流程也有了一定了解。

後面的功能調整基本都可以按照以下三步進行:

  1. service

  2. model

  3. component 我們現在開始增加使用者刪除功能。

  4. service, 修改 src/pages/users/services/users.js

export function remove(id) {
  return request(`/api/users/${id}`, {
    method: 'DELETE',
  });
}
複製程式碼
  1. model, 修改 src/pages/users/model.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 } });
},
複製程式碼
  1. component, 修改 src/pages/users/components/Users.js,替換 deleteHandler 內容:
dispatch({
  type: 'users/remove',
  payload: id,
});
複製程式碼

注意由於使用第三方api網站,資料並不會真的刪除,只是刪除api返回成功,選擇刪除後重新獲取資料,仍然是原來的資料。

Step 10. 處理使用者編輯(與umi@1相同)

處理使用者編輯和前面的一樣,遵循三步走:

  1. service
  2. model
  3. 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/model.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 狀態,有幾種選擇:

  1. 存 dva 的 model state 裡
  2. 存 component state 裡

另外,怎麼存也是個問題,可以:

  1. 只有一個 visible,然後根據使用者點選的 user 填不同的表單資料
  2. 幾個 user 幾個 visible 此教程選的方案是 2-2,即存 component state,並且 visible 按 user 存。另外為了使用的簡便,封裝了一個 UserModal 的元件。

完成後,切換到瀏覽器,應該就能對使用者進行編輯了。

Step 11. 處理使用者建立(與umi@1相同)

相比使用者編輯,使用者建立更簡單些,因為可以共用 UserModal 元件。和 Step 10 比較類似,就不累述了,詳見 Commit


到這裡,我們已經完成了一個完整的 CURD 應用。如果感興趣,可以進一步看下 dva 和 umi 的資料:

(完)

總結

本文主要使用umi@2來完成 umi-dva-user-dashboard 專案。可以看到在業務程式碼上,和umi@1的寫法基本一致,遷移成本比較低。目前手頭的專案正在使用dva進行重構,暫時還沒有使用 umi@2 的計劃,不過會持續關注 umi 的成長的。

相關文章