公司项目有些用到了DvaJS[D.va]
,为了更好的使用,这些调研总结下。
概述
React and redux based, lightweight and elm-style framework.
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。
dva做了三件事
把 store 及 saga 统一为一个 model 的概念, 写在一个 js 文件里面
增加了一个 Subscriptions, 用于收集其他来源的 action, eg: 键盘操作
model写法很简约, 类似于 DSL 或者 RoR, coding 快得飞起✈️
可以看出dva主要解决的也是围绕redux的数据流转,同时补充了react-router,fetch。
使用 dva.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 export default { namespace : 'products' , state : [], reducers : { 'delete' (state, { payload : id }) { return state.filter(item => item.id !== id); }, }, subscriptions : { } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const app = dva({ initialState : { products : [ { name : 'dva' , id : 1 }, { name : 'antd' , id : 2 }, ], }, }); app.model(productsModel.default); app.router(() => <ConnectedApp /> ); app.start('#root' );
model复用 dva-model-extend
Utility method to extend dva model.
https://github.com/dvajs/dva-model-extend
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import modelExtend from 'dva-model-extend' ;const human = { state : { stomach : null , }, reducers : { eat (state, { payload: food } ) { return { ...state, stomach : food }; }, }, }; const benjy = modelExtend(human, { namespace : 'human.benjy' , state : { name : 'Benjy' , }, });
源码
项目使用lerna 进行多包管理
1 2 3 4 ├── dva // Official React bindings for dva, with react-router@4. ├── dva-core // The core lightweight library for dva, based on redux and redux-saga. ├── dva-immer // Create the next immutable state tree by simply modifying the current tree └── dva-loading // Auto loading data binding plugin for dva. :clap: You don't need to write `showLoading` and `hideLoading` any more.
代码量
/packages中JS源码,统计脚本cloc . --md --exclude-dir=test --include-ext=js
Language
files
blank
comment
code physical lines
JavaScript
26
128
110
962
——–
——–
——–
——–
——–
SUM:
26
128
110
962
从代码量也可以看出dva封装的很轻。
函数实现 dva() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 export default function (opts = {} ) { const history = opts.history || createHashHistory(); const createOpts = { initialReducer : { router : connectRouter(history), }, setupMiddlewares (middlewares ) { return [routerMiddleware(history), ...middlewares]; }, setupApp (app ) { app._history = patchHistory(history); }, }; const app = create(opts, createOpts); const oldAppStart = app.start; app.router = router; app.start = start; return app; ... }
model() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function injectModel (createReducer, onError, unlisteners, m ) { m = model(m); const store = app._store; store.asyncReducers[m.namespace] = getReducer(m.reducers, m.state, plugin._handleActions); store.replaceReducer(createReducer()); if (m.effects) { store.runSaga(app._getSaga(m.effects, m, onError, plugin.get('onEffect' ), hooksAndOpts)); } if (m.subscriptions) { unlisteners[m.namespace] = runSubscription(m.subscriptions, m, app, onError); } }
subscriptions 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 export function run (subs, model, app, onError ) { const funcs = []; const nonFuncs = []; for (const key in subs) { if (Object .prototype.hasOwnProperty.call(subs, key)) { const sub = subs[key]; const unlistener = sub( { dispatch : prefixedDispatch(app._store.dispatch, model), history : app._history, }, onError, ); if (isFunction(unlistener)) { funcs.push(unlistener); } else { nonFuncs.push(key); } } } return { funcs, nonFuncs }; }
start() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 function start (container ) { if (isString(container)) { container = document .querySelector(container); invariant(container, `[app.start] container ${container} not found` ); } invariant( !container || isHTMLElement(container), `[app.start] container should be HTMLElement` , ); invariant(app._router, `[app.start] router must be registered before app.start()` ); if (!app._store) { oldAppStart.call(app); } const store = app._store; app._getProvider = getProvider.bind(null , store, app); if (container) { render(container, store, app, app._router); app._plugin.apply('onHmr' )(render.bind(null , container, store, app)); } else { return getProvider(store, this , this ._router); } }
plugin/use() dva中对于类的使用只有两个地方,一个是动态加载组件,一个就是插件定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 use (plugin ) { invariant(isPlainObject(plugin), 'plugin.use: plugin should be plain object' ); const { hooks } = this ; for (const key in plugin) { if (Object .prototype.hasOwnProperty.call(plugin, key)) { invariant(hooks[key], `plugin.use: unknown plugin property: ${key} ` ); if (key === '_handleActions' ) { this ._handleActions = plugin[key]; } else if (key === 'extraEnhancers' ) { hooks[key] = plugin[key]; } else { hooks[key].push(plugin[key]); } } } }
写在最后 通过源码阅读可以看到dva封装比较浅,但解决了围绕redux的逻辑组织问题,同时提供了路由配置/订阅,插件拓展。
相关文档