公司项目有些用到了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的逻辑组织问题,同时提供了路由配置/订阅,插件拓展。
相关文档