内容简介:dva源码解析(一)
写在前面
dva
是蚂蚁金服推出的一个单页应用框架,对 redux
, react-router
, redux-saga
进行了上层封装,没有引入新的概念,但是极大的程度上提升了开发效率;下面内容为本人理解,如有错误,还请指出,不胜感激。
redux的痛苦
redux
的优点很多,痛点也有,比如异步控制, redux-saga
的出现使得异步操作变得优雅,但是基于 redux-saga
不得不承认的一点就是开发过程实在是太麻烦了,假若增加一个操作,不得不操作 actions
, reducers
, sagas
,对于 sagas
可以还需要进行 watch
,而后还要进行 fork
;(PS: 本来就够麻烦了,再加上一个 sagas
);在添加一个操作时,不得不操作这么多的文件,实在是麻烦,而 dva
的出现在一定程度上解决了这个问题。
dva基本概念
未使用 dva
下的目录经常是这样的:
actions --/ user.js --/ team.js reducers --/ user.js --/ team.js sagas/ --/ user.js --/ team.js
dva
将其合并:
models --/ user.js --/ team.js
dva
中有着几个概念:
namespace => combineReducers中对应的key值 state => 对应初始的state,也就是initialState effects => saga的处理函数 reducers => 对应reducers,不同的是,写法上将switch...case转化为对象
除了这些以外, dva
中还有 subscriptions
,这一概念来源于 elm
,
dva的实现
初始化
const app = dva({ history: browserHistory });
上面的过程发生了什么?
dva
本质上调用了下面函数:
function dva(hooks = {}) { const history = hooks.history || defaultHistory; const initialState = hooks.initialState || {}; delete hooks.history; delete hooks.initialState; const plugin = new Plugin(); plugin.use(hooks); const app = { // properties _models: [], _router: null, _store: null, _history: null, _plugin: plugin, _getProvider: null, // methods use, model, router, start, }; return app; }
hooks
为传入的一些配置,例如可以通过传入 history
来改变路由的实现, dva
默认采用的是 hashHistory
;从这里可以看出 dva
暴露出来的方法:
-
app.router()
:指定路由,需要传入一个函数,一般类似于({ history }) => (<Router>...</Router>)
-
app.use()
:添加插件,这个稍后来看~ -
app.model()
:添加model
,也就是对应的添加一个store
下的数据,该方法做的就是对传入的model
进行检查,对reducers
添加命名空间,而后将其push
到_models
中。-
namespace
必须且唯一,因为内置了react-redux-router
,所以namespace
也不能为routing
-
subscriptions
与effects
均为可选参数,传入的话必须为对象 -
reducers
为可选,支持对象和数组两种传入方式(传入数组的方式,往往伴随着高阶reducer
的应用,具体稍后再看~)
-
-
app.start()
:初始化应用,接受参数为选择器或者DOM
节点
需要注意的是:
-
reducers
和effects
的key
不需要用namespace/action
的形式了,因为dva
会自动将其加上,dispatch
的时候,saga
需要加上namespace
,而saga
中的put
不需要加入namespace
,原因是dva
对put进行了重载
-
dva
同时支持rn应用,引入dva/mobile
即可,这时react-router
不在需要,利用rn中的Navigator
即可,不会引用react-router
与react-redux-router
,namespace
可以命名为routing
;正是由于这点差异,作者将路由相关的内容作为参数传入了进去,具体可以参见这个文件。
创建
将一些配置项初始化好后,就可以 app.start
就是来创建一个应用,下面就一点点的看看 start
的过程(以下基于默认情况,也就是使用了 react-router
):
-
参数校验,是否为
DOM
元素或者检查是否可以根据传入的选择器字符串找到对应的DOM
,这个DOM
对应的就是ReactDOM.render
的第二个参数。 -
错误处理,使得发生错误时,不至于应用奔溃,当然需要传入自定义
hooks.onError
来处理:
// 传入hooks.onError则调用,反之调用默认函数处理,抛出异常即可 const onError = plugin.apply('onError', (err) => { throw new Error(err.stack || err); }); // 目的是出现错误时,也可以进行dispatch操作 const onErrorWrapper = (err) => { if (err) { if (typeof err === 'string') err = new Error(err); onError(err, app._store.dispatch); } };
-
遍历
_models
,初始化reducers,sagas
const sagas = []; // initalReducer为{ routing: routerReducer } const reducers = { ...initialReducer }; // 为rootReducer for (const m of this._models) { // 得到默认的state reducers[m.namespace] = getReducer(m.reducers, m.state); if (m.effects) sagas.push(getSaga(m.effects, m, onErrorWrapper)); }
处理reducers
对于 redux
的 reducers
最常见的是基于 switch..case
的,而 dva
做出了一些改变,将每一个 case
分支变作了一个函数:
(PS: 本人认为,这个可以块可以更改,利用 some
操作来尽可能少的调用无意义的 reducer
,于是我提了一个pr)
每一个 reducer
的实现如下:
// actionType对应的是dva的reducers中的key值 (state, action) => { const { type } = action; if (type && actionType !== type) { return state; } return reducer(state, action); };
处理sagas
看完了对于 reducers
的处理,下面来看一下对于 sagas
的处理:
function getSaga(effects, model, onError) { return function *() { for (const key in effects) { if (Object.prototype.hasOwnProperty.call(effects, key)) { const watcher = getWatcher(key, effects[key], model, onError); const task = yield sagaEffects.fork(watcher); // 为了移除时可以将saga任务注销 yield sagaEffects.fork(function *() { yield sagaEffects.take(`${model.namespace}/@@CANCEL_EFFECTS`); yield sagaEffects.cancel(task); }); } } }; }
getWatcher
返回一个 saga
监听函数,也就是通常写的 watchXXX
, model.effects[key]
可以是一个任务函数;也可以是个数组,第一个参数为任务函数,第二为配置对象,可以传入 type
, type
有4个可选值, takeEvery
(默认), takeLatest
, throttle
, watcher
四种, dva
对 effects
做了一个错误处理:
effect => function *(...args) { try { yield effect(...args.concat(createEffects(model))); } catch (e) { onError(e); // 为之前的onErrorWrapper } }
注意:
-
watcher
是指传入的任务函数就是一个watcher
直接fork
就好 -
throttle
还要传入一个ms
配置,这个ms
代表着在多少毫秒内只触发一次同一类型saga
任务,而takeEvery
是不会限制同一类型执行次数,takeLatest
只能执行一个同一类型任务,有执行中的再次执行就会取消 -
由
getSaga
可以看出,${namespace}/@@CANCEL_EFFECTS
可以取消对应的任务监听 -
可以通过配置
hooks.onEffect
来增加saga
的watcher
增强 redux
-
redux
中间件,由sagaMiddware
,routerMiddware
(启用react-router
时),hooks.onAction
传入的其它中间件,如redux-logger
等 -
其它增强,如
redux-devtools
,内置了redux-devtools
,另需的话在hooks.extraReducers
传入
const enhancers = [ applyMiddleware(...middlewares), devtools(), ...extraEnhancers, ]; const store = this._store = createStore( // eslint-disable-line createReducer(), initialState, compose(...enhancers), );
设置redux的回调函数
通过配置 hooks.onStateChange
可以指定 redux
的 state
改变后所触发的回调函数:
const listeners = plugin.get('onStateChange'); for (const listener of listeners) { store.subscribe(() => { listener(store.getState()); }); } }
新概念subscriptions
subscriptions
是一个新概念,会在 dom ready
之后执行,在这里面可以做一些基础数据的获取:
一般会将初始数据的获取放在 react
的生命周期中,比如 componentWillMount
,但是假设我们做了代码分割,实现了按需加载,那么我们开始获取数据的时间为:获取相应的 js
+解析 js
+执行 react
生命周期,但是 redux
的数据加载和 ui
组件没有太大关系,可以将数据获取的时间点提前, subscriptions
提供了解决方法,其意义为订阅,对于上面的场景,我们可以订阅路由,到了执行的路由执行相应的 dispatch()
,如:
setup({ dispatch, history }) { return history.listen(({ pathname, query }) => { if (pathname === '/users') { dispatch({ type: 'fetch', payload: query }); } }); }
(PS: 对于这个新概念,我也不是很清楚,后面的文章会有专门的描述,大家先有一个概念就好)
挂载
上述过程均为初始化的过程,就是获取到需要的 reducers
, sagas
以及对于一些中间件和插件的配置,下面要进行的就是挂载了,也就熟悉的 render(<Provider>, container)
。
动态处理model
dva.model
与 dva.unmodel
,封装了在运行时的 store
进行一类增加和删除的操作,例如可以再切换到某一路由时动态的加入一个 model
(个人猜测,热更新很有可能也利用了这个两个 api
与 hooks.onHmr
)。
未完结
关于 redux
还有一个利器,那就是高阶 reduce
,当然在 dva
中也有体现,这篇文章已经很长了,这些内容留在下一篇中介绍。以上是本人对于 dva
的粗略的理解,内容如有错误,还请大家指出。 dva
的确简化了开发的流程,而且在蚂蚁金服的很多业务线也有着应用,是一个很值得大家一试!
以上所述就是小编给大家介绍的《dva源码解析(一)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- ReactNative源码解析-初识源码
- Spring源码系列:BeanDefinition源码解析
- Spring源码分析:AOP源码解析(下篇)
- Spring源码分析:AOP源码解析(上篇)
- 注册中心 Eureka 源码解析 —— EndPoint 与 解析器
- 新一代Json解析库Moshi源码解析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Seasoned Schemer
Daniel P. Friedman、Matthias Felleisen / The MIT Press / 1995-12-21 / USD 38.00
drawings by Duane Bibbyforeword and afterword by Guy L. Steele Jr.The notion that "thinking about computing is one of the most exciting things the human mind can do" sets both The Little Schemer (form......一起来看看 《The Seasoned Schemer》 这本书的介绍吧!