初識React(9):dva簡介

前言

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

import dva from 'dva';

// 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');

dva僅有6個api,如下介紹:

1. const app = dva(options)

創建應用,返回dva實例

options中包含:

(1) history:默認爲hashHistory,如果要配置history爲browserHistory,則

import createHistory from 'history/createBrowserHistory';
const app = dva({
  history: createHistory(),
});

(2) initialState: 指定初始數據,優先級高於model中的state,默認爲{}

(3) onError: 管理全局出錯狀態,如下:

const app = dva({
  onError(e){
    console.log(e);
  }
});

(4) onAction(fn | fn[]): 在action被dispatch時觸發,用於註冊redux中間件,支持函數格式或者函數數組格式,如下通過redux-logger答應日誌,如:

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

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

(6) 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 };
    },
  },
});

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

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

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

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

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

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

2.app.use(hooks)

配置 hooks 或者註冊插件。(插件最終返回的是 hooks )

比如註冊 dva-loading 插件的例子:

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

hooks 包含2中(3)到(10)

3.app.model(model)

註冊model

model 是 dva 中最重要的概念,以下是典型的例子:

app.model({
  namespace: 'todo',
  state: [],
  reducers: {
    add(state, { payload: todo }) {
      // 保存數據到 state
      return [...state, todo];
    },
  },
  effects: {
    *save({ payload: todo }, { put, call }) {
      // 調用 saveTodoToServer,成功後觸發 `add` action 保存到 state
      yield call(saveTodoToServer, todo);
      yield put({ type: 'add', payload: todo });
    },
  },
  subscriptions: {
    setup({ history, dispatch }) {
      // 監聽 history 變化,當進入 `/` 時觸發 `load` action
      return history.listen(({ pathname }) => {
        if (pathname === '/') {
          dispatch({ type: 'load' });
        }
      });
    },
  },
});

model 包含 5 個屬性:

namespace: model 的命名空間,同時也是他在全局 state 上的屬性,只能用字符串,不支持通過 . 的方式創建多層命名空間。

state: 初始值,優先級低於傳給 dva() 的 opts.initialState,如下:

const app = dva({
  initialState: { count: 1 },
});
app.model({
  namespace: 'count',
  state: 0,
});

此時,在 app.start() 後 state.count 爲 1

reducers: 以 key/value 格式定義 reducer。用於處理同步操作,唯一可以修改 state 的地方。由 action 觸發,格式爲 (state, action) => newState 或 [(state, action) => newState, enhancer]

effects: 以 key/value 格式定義 effect。用於處理異步操作和業務邏輯,不直接修改 state。由 action 觸發,可以觸發 action,可以和服務器交互,可以獲取全局 state 的數據等等。格式爲

*(action, effects) => void 或 [*(action, effects) => void, { type }]。

subscriptions: 以 key/value 格式定義 subscription。subscription 是訂閱,用於訂閱一個數據源,然後根據需要 dispatch 相應的 action。在 app.start() 時被執行,數據源可以是當前的時間、服務器的 websocket 連接、keyboard 輸入、geolocation 變化、history 路由變化等等。格式爲 ({ dispatch, history }, done) => unlistenFunction。注意:如果要使用 app.unmodel(),subscription 必須返回 unlisten 方法,用於取消數據訂閱。

4.app.unmodel(namespace)

取消 model 註冊,清理 reducers, effects 和 subscriptions。subscription 如果沒有返回 unlisten 函數,使用 app.unmodel 會給予警告

5.app.router(({ history, app }) => RouterConfig)

註冊路由表。通常是這樣的:

import { Router, Route } from 'dva/router';
app.router(({ history }) => {
  return (
    <Router history={history}>
      <Route path="/" component={App} />
    <Router>
  );
});

推薦把路由信息抽成一個單獨的文件,這樣結合 babel-plugin-dva-hmr 可實現路由和組件的熱加載,比如:

app.router(require('./router'));

而有些場景可能不使用路由,比如多頁應用,所以也可以傳入返回 JSX 元素的函數。比如:

app.router(() => <App />);

6.app.start(selector)

啓動應用。selector 可選,如果沒有 selector 參數,會返回一個返回 JSX 元素的函數。

app.start('#root');

那麼什麼時候不加 selector?常見場景有測試、node 端、react-native 和 i18n 國際化支持。

比如通過 react-intl 支持國際化的例子:

import { IntlProvider } from 'react-intl';
...
const App = app.start();
ReactDOM.render(<IntlProvider><App /></IntlProvider>, htmlElement);

本文參考官網:https://dvajs.com/api/#dva-api

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