DvaJS的學習之路1 - dva+umi官網例子學習

前置知識

umi + dva,完成用戶管理的 CURD 應用

本文主要是在 dva 做者 @sorrycc 的例子 umi + dva,完成用戶管理的 CURD 應用 基礎上進行的一些本身的學習記錄。 關於評論區的小夥伴說的照抄 @sorrycc 大神的github文章的問題,這裏迴應下,沒錯,本文 90% 以上的文字都出自 umi + dva,完成用戶管理的 CURD 應用 這篇文章,並且做者和文章出處一開始就交代清楚了,寫這個文章的目的是記錄一下本身跟着教程 step by step 完成這個應用的過程,並非教程,指望教程的小夥伴, @sorrycc 大大在 dva 官網上提供了不少豐富的例子,能夠參考。css

2018年9月10日更新,umi 已經升級到了 2.0 版本,但願使用 umi@2 開發的同窗請移步至 DvaJS的學習之路2 - umi@2 + dva,完成用戶管理的 CURD 應用html

開始以前:

  • 確保 node 版本是 8.4 或以上
  • 用 cnpm 或 yarn 能節約你安裝依賴的時間

Step 1. 安裝 dva-cli@next 並建立應用

先安裝 dva-cli,並確保版本是 1.0.0-beta.2 或以上。node

$ 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 方式來初始化項目。詳見:例子和腳手架react

而後建立應用:git

$ dva new user-dashboard
$ cd user-dashboard
複製代碼

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

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

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

而後啓動應用:(這個命令一直開着,後面不須要重啓)github

$ npm start
複製代碼

瀏覽器會自動開啓,並打開 http://localhost:8000npm

image

訪問 http://localhost:8000/api/users ,就能訪問到 jsonplaceholder.typicode.com/users 的數據。(因爲 typicode.com 服務的穩定性,偶爾可能會失敗。不過不要緊,正好便於咱們以後對於出錯的處理)json

image

Step 3. 生成 users 路由

umi 中文件即路由,因此咱們要新增路由,新建文件便可,詳見 umijs.org/guide/route…api

新建 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 參數默認爲 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 });
        }
      });
    },
  },
};
複製代碼

這裏面有一些寫法以前沒有用過:好比{ 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/fetchusers/save 的 action 以及相關的 state 。

image

Step 5. 添加界面,讓用戶列表展示出來

咱們把組件存在 src/pages/users/components 裏,因此在這裏新建 Users.jsUsers.css。具體參考這個 Commit

需留意兩件事:

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

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

image

有幾點須要注意:

  • Users.js 裏面使用了 antd 的組件,可是項目並無手動安裝 antd, 原來是 umi 幫咱們引入了 antd 。
  • Users.js 裏面將model和組件鏈接了起來,注意 const { list, total, page } = state.users; 裏面的 usersmodel 裏面的 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

注意:

頁頭的菜單會隨着頁面切換變化,高亮顯示當前頁所在的菜單項

image

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 就好。

處理分頁有兩個思路:

  1. 發 action,請求新的分頁數據,保存到 model,而後自動更新頁面
  2. 切換路由 (因爲以前監聽了路由變化,因此後續的事情會自動處理)

咱們用的是思路 2 的方式,好處是用戶能夠直接訪問到 page 2 或其餘頁面。

參考這個 Commit

Step 9. 處理用戶刪除

通過前面的 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/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 } });
},
複製代碼
  1. component, 修改 src/pages/users/components/Users.js,替換 deleteHandler 內容:
dispatch({
  type: 'users/remove',
  payload: id,
});
複製代碼

切換到瀏覽器,刪除功能應該已經生效。

Step 10. 處理用戶編輯

處理用戶編輯和前面的同樣,遵循三步走:

  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/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 狀態,有幾種選擇:

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

另外,怎麼存也是個問題,能夠:

  1. 只有一個 visible,而後根據用戶點選的 user 填不一樣的表單數據
  2. 幾個 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 學習的文章。

相關文章
相關標籤/搜索