初學DvaJS,掌握核心概念後原來如此簡單

dva是什麼

dva 首先是一個基於 redux和 redux-saga的數據流方案,然後爲了簡化開發體驗,dva 還額外內置了 react-router和 fetch,所以也可以理解爲一個輕量級的應用框架。

dva 是基於現有應用架構 (redux + react-router + redux-saga 等)的一層輕量封裝。dva 是 react 和 redux 的最佳實踐。最核心的是提供了 app.model 方法,用於把 reducer, initialState, action, saga 封裝到一起。

特性

  • 易學易用,僅有 6 個 api,對 redux 用戶尤其友好,配合 umi 使用更是降低爲 0 API
  • elm 概念,通過 reducers, effects 和 subscriptions 組織 model
  • 插件機制,比如 dva-loading可以自動處理 loading 狀態,不用一遍遍地寫 showLoading 和 hideLoading
  • 支持 HMR,基於 babel-plugin-dva-hmr實現 components、routes 和 models 的 HMR

安裝

創建一個dva項目很簡單,dva爲我們提供了dva-cli,用來快速創建dva項目。

通過 npm 安裝 dva-cli 並確保版本是 0.9.1 或以上。

$ npm install dva-cli -g
$ dva -v
dva-cli version 0.10.1
    dva version 2.4.1
roadhog version 2.5.0-beta.4

創建應用

$ dva new dva-quickstart

運行

$ cd dva-quickstart
$ npm start
​
Compiled successfully!
​
You can now view Your App in the browser.
​
  Local:            http://localhost:8000/
  On Your Network:  http://192.168.43.29:8000/
​
Note that the development build is not optimized.
To create a production build, use npm run build.

運行成功後,會自動打開瀏覽器,默認爲4000端口,可以看到下面的界面

目錄結構

│  .editorconfig     # 編輯器配置
│  .eslintrc         # eslint配置
│  .gitignore        # 配置git忽略目錄或文件
│  .roadhogrc.mock.js# mock數據配置
│  .webpackrc        # webpack配置文件
│  package.json      # 項目配置文件
├─mock               # 模擬數據文件
├─public             # 公共資源
└─src
    │  index.css     # 主樣式
    │  index.js      # 入口文件
    │  router.js     # 路由配置文件
    ├─assets         # 放置靜態資源
    ├─components     # 組件
    ├─models         # dva最核心的內容
    ├─routes         # 路由頁面
    ├─services       # 請求後臺數據,調用後臺API接口
    └─utils          # 工具類,默認放置了封裝fetch的方法

Dva的核心概念

數據的改變發生通常是通過用戶交互行爲或者瀏覽器行爲(如路由跳轉等)觸發的,當此類行爲會改變數據的時候可以通過 dispatch 發起一個 action,如果是同步行爲會直接通過 Reducers 改變 State ,如果是異步行爲(副作用)會先觸發 Effects 然後流向 Reducers 最終改變 State,所以在 dva 中,數據流向非常清晰簡明,並且思路基本跟開源社區保持一致(也是來自於開源社區)。

Dva解決了react沒有解決的問題

  • 通信:組件之間如何通信?
  • 數據流:數據如何和視圖串聯起來?路由和數據如何綁定?如何編寫異步邏輯?等等

通信問題
組件會發生三種通信。

  • 向子組件發消息
  • 向父組件發消息
  • 向其他組件發消息

React 只提供了一種通信手段:傳參。對於大應用,很不方便。

數據流問題
目前流行的數據流方案有:

  • Flux,單向數據流方案,以 Redux 爲代表
  • Reactive,響應式數據流方案,以 Mobx 爲代表
  • 其他,比如 rxjs 等

最流行的社區 React 應用架構方案如下:

  • 路由:React-Router
  • 架構:Redux
  • 異步操作:Redux-saga
    這種方案雖然說可以達到所需要的目的,但是缺點也顯而易見,要引入多個庫,項目結構複雜。

那麼dva所做的事情就是把他們進行了整合與封裝,只暴露出幾個簡單的API,使得react項目的開發變得方便快捷。

在index.js中,可以看到dva執行的步驟

import dva from 'dva';
import './index.css';// 1. Initialize
const app = dva({});
// 2. Plugins
// app.use({});
// 3. Model
app.model(require('./models/example').default);
// 4. Router
app.router(require('./router').default);
// 5. Start
app.start('#root');

Model

首先看一下Model,這是dva的核心模塊,組件之前的數據狀態管理全都要靠它,通過Model可以解決react各組件間的傳值問題。

一個Model就是一個對象,它包含以下屬性:

  • namespace: 當前 Model 的名稱。整個應用的 State,由多個小的 Model 的 State 以 namespace 爲 key 合成
  • state: 該 Model 當前的狀態。數據保存在這裏,直接決定了視圖層的輸出
  • reducers: Action 處理器,處理同步動作,用來算出最新的 State
  • effects:Action 處理器,處理異步動作

reducers中包含多個Action 處理器,它接受兩個參數,一個是當前的state,另一個是傳入的參數,返回處理後的state來更新當前的state。

reducers: {
delete(state, { payload: id }) {
return state.filter(item => item.id !== id);
  },
},

‍Effect 是一個generator函數,內部使用yield關鍵字,標識每一步的操作(不管是同步還是異步),dva 提供多個 effect 函數內部的處理函數,比較常用的是 call 和 put。 call 用來執行異步操作, put用來發出一個Action,類似於 dispatch。就像下面這樣:

effects:{
  *deleteItem(action, { call, put}){
yield new Promise(resolve => {
      setTimeout(() => {
        resolve()
      }, 1000);
    })
yield put({
type: 'delete',
      payload: action.payload
    })
  }
}

State只是一個存儲數據的地方,而數據的表現還是要在UI層進行呈現,還要可以實現用戶操作UI層改變State的目的,State改變後也會引起View的改變。這就需要將State與View關聯起來。

dva提供了一個叫connect的方法,connect 方法傳入的第一個參數是 mapStateToProps 函數,mapStateToProps 函數會返回一個對象,用於建立 State 到 Props 的映射關係。它返回的也是一個 React 組件,通常稱爲容器組件。

const Products = ({ dispatch, products }) => {
function handleDelete(id) {
    dispatch({
type: 'products/deleteItem',
payload: id,
    });
  }
return (
<div>
<h2>List of Products</h2>
<ProductList onDelete={handleDelete} products={products} />
</div>
  );
};export default connect(({ products }) => ({
  products,
}))(Products);

在handleDelete中有一個dispatch方法,被 connect 的 Component 會自動在 props 中擁有 dispatch 方法,它就是用來改變state的方法。其中的type的值是以“namespace/effect或reducer”的形式,payload爲傳入的額外參數。

dva的其他輸出文件

dva/router

定義路由,默認輸出 react-router接口, react-router-redux的接口通過屬性 routerRedux 輸出。

import { Router, Route, routerRedux } from 'dva/router';

dva/fetch

異步請求庫,輸出 isomorphic-fetch的接口。不和 dva 強綁定,可以選擇任意的請求庫。

import fetch from 'dva/fetch';function parseJSON(response) {
  return response.json();
}function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
  return response;
}const error = new Error(response.statusText);
  error.response = response;
  throw error;
}
export default function request(url, options) {
return fetch(url, options)
    .then(checkStatus)
    .then(parseJSON)
    .then(data => ({ data }))
    .catch(err => ({ err }));
}

dva/saga

輸出 redux-saga的接口,主要用於用例的編寫。(用例中需要用到 effects)

dva/dynamic

解決組件動態加載問題的 util 方法。

在實際項目開發中,我們的model數量是很多的,每個model一般都只是在對應的幾個模塊中會用到,但在dva的初始化過程中,我們就必須把所有要用的model加載進來,這顯然對性能方面是很不友好的。如果能在對應的路由下只加載所用到的model,這將會得到很大的提升。

import dynamic from 'dva/dynamic';const UserPageComponent = dynamic({
  app,
models: () => [
import('./models/users'),
  ],
component: () => import('./routes/UserPage'),
});
  • app: dva 實例,加載 models 時需要
  • models: 返回 Promise 數組的函數,Promise 返回 dva model
  • component:返回 Promise 的函數,Promise 返回 React Component

插件機制

dva中可以靈活的配置 hooks 或者註冊插件,如dva-loading 可以自動處理 loading 狀態,不用一遍遍地寫 showLoading 和 hideLoading

import createLoading from 'dva-loading';
...
app.use(createLoading(opts));
app = dva(opts)

app = dva(opts)
opts中可以設置 history 和初始化 state 數據,這裏的 state 優先級要高於model中的state。

const app = dva({
  history: createHistory(),
  initialState: {  //初始數據
     products: [
       { name: 'dva', id: 1 },
       { name: 'antd', id: 2 },
     ],
   },
 });

​還可以在這裏面設置以下hooks:

onError((err, dispatch) => {})

effect 執行錯誤或 subscription 通過 done 主動拋錯時觸發,可用於管理全局出錯狀態。如果我們用 antd,那麼最簡單的全局錯誤處理通常會這麼做:

import { message } from 'antd';
const app = dva({
  onError(e) {
    message.error(e.message, /* duration */3);
  },
});

onAction(fn | fn[])
在 action 被 dispatch 時觸發,用於註冊 redux 中間件。支持函數或函數數組格式。例如我們要通過 redux-logger打印日誌:

import createLogger from 'redux-logger';
const app = dva({
  onAction: createLogger(opts),
});

onStateChange(fn)
state 改變時觸發,可用於同步 state 到 localStorage,服務器端等。

onReducer(fn)
封裝 reducer 執行。比如藉助 redux-undo實現 redo/undo :

import undoable from 'redux-undo';
const app = dva({
  onReducer: reducer => {
    return (state, action) => {
      const undoOpts = {};
      const newState = undoable(reducer, undoOpts)(state, action);
      // 由於 dva 同步了 routing 數據,所以需要把這部分還原
      return { ...newState, routing: newState.present.routing };
    },
  },
});

onEffect(fn)
封裝 effect 執行。比如 dva-loading基於此實現了自動處理 loading 狀態。

onHmr(fn)
熱替換相關,目前用於 babel-plugin-dva-hmr。

extraReducers
指定額外的 reducer,比如 redux-form需要指定額外的 form reducer:

import { reducer as formReducer } from 'redux-form'
const app = dva({
  extraReducers: {
    form: formReducer,
  },
});

extraEnhancers
指定額外的 StoreEnhancer,比如結合 redux-persist的使用:

import { persistStore, autoRehydrate } from 'redux-persist';
const app = dva({
  extraEnhancers: [autoRehydrate()],
});
persistStore(app._store);

歡迎訪問我的個人網站:www.dengzhanyong.com

喜歡的話可以關注我的公衆號:【前端筱園】

一起交流,共同成長在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章