react學(xué)習(xí)筆記 —— dva

什么是dva?

dva 首先是一個(gè)基于 redux 和 redux-saga 的數(shù)據(jù)流方案,然后為了簡(jiǎn)化開發(fā)體驗(yàn),dva 還額外內(nèi)置了 react-router 和 fetch ,所以也可以理解為一個(gè)輕量級(jí)的應(yīng)用框架。

dva 應(yīng)用的最簡(jiǎn)結(jié)構(gòu)(帶 model)

// 創(chuàng)建應(yīng)用
const app = dva();

// 注冊(cè) Model
app.model({
  namespace: 'count',
  state: 0,
  reducers: {
    add(state) { return state + 1 },
  },
  effects: {
    *addAfter1Second(action, { call, put }) {
      yield call(delay, 1000);
      yield put({ type: 'add' });
    },
  },
});

// 注冊(cè)視圖
app.router(() => <ConnectedApp />);

// 啟動(dòng)應(yīng)用
app.start('#root');

dva
默認(rèn)輸出文件。
react-router 接口, react-router-redux 的接口通過(guò)屬性 routerRedux 輸出。

在 dva 中,通常需要 connect Model的組件都是 Route Components,組織在/routes/目錄下,而/components/目錄下則是純組件(Presentational Components)

API

輸出文件

dva

默認(rèn)輸出文件。

dva/router

默認(rèn)輸出 react-router
接口, react-router-redux

的接口通過(guò)屬性 routerRedux 輸出。

比如:

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

dva/fetch

異步請(qǐng)求庫(kù),輸出 isomorphic-fetch

的接口。不和 dva 強(qiáng)綁定,可以選擇任意的請(qǐng)求庫(kù)。

dva/saga

輸出 redux-saga

的接口,主要用于用例的編寫。(用例中需要用到 effects)

dva/dynamic

解決組件動(dòng)態(tài)加載問(wèn)題的 util 方法。

比如:

import dynamic from 'dva/dynamic';

const UserPageComponent = dynamic({
app,
models: () => [
import('./models/users'),
],
component: () => import('./routes/UserPage'),
});

opts 包含:

app: dva 實(shí)例,加載 models 時(shí)需要
models: 返回 Promise 數(shù)組的函數(shù),Promise 返回 dva model
component:返回 Promise 的函數(shù),Promise 返回 React Component

dva API

app = dva(opts)

創(chuàng)建應(yīng)用,返回 dva 實(shí)例。(注:dva 支持多實(shí)例)

opts 包含:

history:指定給路由用的 history,默認(rèn)是 hashHistory
initialState:指定初始數(shù)據(jù),優(yōu)先級(jí)高于 model 中的 state,默認(rèn)是 {}

如果要配置 history 為 browserHistory,可以這樣:

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

另外,出于易用性的考慮,opts 里也可以配所有的 hooks ,下面包含全部的可配屬性:

const app = dva({
history,
initialState,
onError,
onAction,
onStateChange,
onReducer,
onEffect,
onHmr,
extraReducers,
extraEnhancers,
});

app.use(hooks)

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

比如注冊(cè) dva-loading

插件的例子:

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

hooks 包含:

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

effect 執(zhí)行錯(cuò)誤或 subscription 通過(guò) done 主動(dòng)拋錯(cuò)時(shí)觸發(fā),可用于管理全局出錯(cuò)狀態(tài)。

注意:subscription 并沒(méi)有加 try...catch,所以有錯(cuò)誤時(shí)需通過(guò)第二個(gè)參數(shù) done 主動(dòng)拋錯(cuò)。例子:

app.model({
subscriptions: {
setup({ dispatch }, done) {
done(e);
},
},
});

如果我們用 antd,那么最簡(jiǎn)單的全局錯(cuò)誤處理通常會(huì)這么做:

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

onAction(fn | fn[])

在 action 被 dispatch 時(shí)觸發(fā),用于注冊(cè) redux 中間件。支持函數(shù)或函數(shù)數(shù)組格式。

例如我們要通過(guò) redux-logger

打印日志:

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

onStateChange(fn)

state 改變時(shí)觸發(fā),可用于同步 state 到 localStorage,服務(wù)器端等。

onReducer(fn)

封裝 reducer 執(zhí)行。比如借助 redux-undo

實(shí)現(xiàn) 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 數(shù)據(jù),所以需要把這部分還原
return { ...newState, routing: newState.present.routing };
},
},
});

onEffect(fn)

封裝 effect 執(zhí)行。比如 dva-loading

基于此實(shí)現(xiàn)了自動(dòng)處理 loading 狀態(tài)。

onHmr(fn)

熱替換相關(guān),目前用于 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
,比如結(jié)合 redux-persist

的使用:

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

app.model(model)

注冊(cè) model,詳見(jiàn) #Model 部分。

app.unmodel(namespace)

取消 model 注冊(cè),清理 reducers, effects 和 subscriptions。subscription 如果沒(méi)有返回 unlisten 函數(shù),使用 app.unmodel 會(huì)給予警告??。

app.replaceModel(model)

只在app.start()之后可用

替換model為新model,清理舊model的reducers, effects 和 subscriptions,但會(huì)保留舊的state狀態(tài),對(duì)于HMR非常有用。subscription 如果沒(méi)有返回 unlisten 函數(shù),使用 app.unmodel 會(huì)給予警告??。

如果原來(lái)不存在相同namespace的model,那么執(zhí)行app.model操作

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

注冊(cè)路由表。

通常是這樣的:

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

推薦把路由信息抽成一個(gè)單獨(dú)的文件,這樣結(jié)合 babel-plugin-dva-hmr

可實(shí)現(xiàn)路由和組件的熱加載,比如:

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

而有些場(chǎng)景可能不使用路由,比如多頁(yè)應(yīng)用,所以也可以傳入返回 JSX 元素的函數(shù)。比如:

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

app.start(selector?)

啟動(dòng)應(yīng)用。selector 可選,如果沒(méi)有 selector 參數(shù),會(huì)返回一個(gè)返回 JSX 元素的函數(shù)。

app.start('#root');

那么什么時(shí)候不加 selector?常見(jiàn)場(chǎng)景有測(cè)試、node 端、react-native 和 i18n 國(guó)際化支持。

比如通過(guò) react-intl 支持國(guó)際化的例子:

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

Model

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

app.model({
namespace: 'todo',
state: [],
reducers: {
add(state, { payload: todo }) {
// 保存數(shù)據(jù)到 state
return [...state, todo];
},
},
effects: {
*save({ payload: todo }, { put, call }) {
// 調(diào)用 saveTodoToServer,成功后觸發(fā) add action 保存到 state
yield call(saveTodoToServer, todo);
yield put({ type: 'add', payload: todo });
},
},
subscriptions: {
setup({ history, dispatch }) {
// 監(jiān)聽 history 變化,當(dāng)進(jìn)入 / 時(shí)觸發(fā) load action
return history.listen(({ pathname }) => {
if (pathname === '/') {
dispatch({ type: 'load' });
}
});
},
},
});

model 包含 5 個(gè)屬性:

namespace

model 的命名空間,同時(shí)也是他在全局 state 上的屬性,只能用字符串,不支持通過(guò) . 的方式創(chuàng)建多層命名空間。

state

初始值,優(yōu)先級(jí)低于傳給 dva() 的 opts.initialState。

比如:

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

此時(shí),在 app.start() 后 state.count 為 1 。

reducers

以 key/value 格式定義 reducer。用于處理同步操作,唯一可以修改 state 的地方。由 action 觸發(fā)。

格式為 (state, action) => newState 或 [(state, action) => newState, enhancer]。

詳見(jiàn): https://github.com/dvajs/dva/blob/master/packages/dva-core/test/reducers.test.js

effects

以 key/value 格式定義 effect。用于處理異步操作和業(yè)務(wù)邏輯,不直接修改 state。由 action 觸發(fā),可以觸發(fā) action,可以和服務(wù)器交互,可以獲取全局 state 的數(shù)據(jù)等等。

格式為 (action, effects) => void 或 [(action, effects) => void, { type }]。

type 類型有:

takeEvery
takeLatest
throttle
watcher

詳見(jiàn):https://github.com/dvajs/dva/blob/master/packages/dva-core/test/effects.test.js

subscriptions

以 key/value 格式定義 subscription。subscription 是訂閱,用于訂閱一個(gè)數(shù)據(jù)源,然后根據(jù)需要 dispatch 相應(yīng)的 action。在 app.start() 時(shí)被執(zhí)行,數(shù)據(jù)源可以是當(dāng)前的時(shí)間、服務(wù)器的 websocket 連接、keyboard 輸入、geolocation 變化、history 路由變化等等。

格式為 ({ dispatch, history }, done) => unlistenFunction。

注意:如果要使用 app.unmodel(),subscription 必須返回 unlisten 方法,用于取消數(shù)據(jù)訂閱。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • dva.js 簡(jiǎn)介 dva 是阿里前端架構(gòu)師 sorrycc 帶 team 研發(fā)的一套輕量級(jí)前端框架,其目的是盡量...
    那顆星_fcaf閱讀 3,839評(píng)論 0 24
  • dva框架的使用詳解及Demo教程 在前段時(shí)間,我們也學(xué)習(xí)講解過(guò)Redux框架的基本使用,但是有很多同學(xué)在交流群里...
    光強(qiáng)_上海閱讀 61,437評(píng)論 5 55
  • 筆者升級(jí)了 dva 的版本,同時(shí)新增了 umi 的使用,具體可以參考這篇文章 dva理論到實(shí)踐——幫你掃清dva的...
    darrell閱讀 40,542評(píng)論 8 86
  • title: dva的初識(shí)date: 2018-05-28 15:02:34tags: dva 內(nèi)心獨(dú)白 這段時(shí)間...
    Kris_lee閱讀 1,614評(píng)論 0 2
  • L先生今年27歲,研究生畢業(yè),剛出來(lái)找了份體面的工作,本想著好好奮斗一下,卻被家里人接二連三地催著找對(duì)象,每次跟爸...
    素顏嬌陽(yáng)閱讀 590評(píng)論 1 1

友情鏈接更多精彩內(nèi)容