乐趣区

初识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

退出移动版