对于dva这个开发框架,国内从事react的前端工程师多半不会感到陌生,dva完善的开发体系和简单的api,让其被普遍运用到实际工做中。我所在的公司也是长期使用dva做为基础的开发框架,虽然好用,可是随着前端技术的飞速发展,dva彷佛陷入停滞了,从npm官网上看其发版状况看,正式版本2.4.1是三年前发布的,最近一次是2.6.0-beta.22版本,也是半年前发布的,所以 附录【2】文章中指出dva将来不肯定性高的隐患。除此以外,关于dva的effect是否能支持async/await的讨论(见附录【1】连接),也暴露出dva在扩展性的短板。前端
上面简单说了一下dva目前的状况,本文的出发点也就是在dva的effect不支持async/await的问题上,用过dva的都清楚,dva的model层采用generator进行流程控制,虽然功能强大,但开发体验跟async/await比起来仍是差了些,所以我就想实现一版支持async/await的mini-dva,其余研发流程尽可能和dva保持一致。react
从这里开始,咱们就造一个支持async/await的mini-dva吧,取个正式的名字就叫 mini-async-dva ,废话不说了,先看一下mini-saync-dva和dva的一个具体对比吧:git
## dva const Foo = dynamic({ app, models: () => [import('./models/foo')], component: () => import('./pages/Foo'), }); ...... <Route path="/foo" component={Foo} /> ......
## mini-async-dva import Bar from './pages/Bar'; ...... <Route path="/bar"> <Bar /> </Route> ......
## dva export default { namespace: 'foo', state: { list: [] }, effects: { * fetchList({ payload }, { call }) { yield call(delay, 1000); } } };
## mini-async-dva export default { namespace: 'foo', state: { list: [] }, effects: { async fetchList(payload, updateStore) { await delay(); } } };
## dva import React from 'react'; import { connect } from 'dva'; @connect((state) => { return state.bar; }) class Bar extends React.Component { ...... } export default Bar;
## mini-async-dva import React from 'react'; import model from '@/model'; @model('bar') class Bar extends React.Component { ...... } export default Bar;
经过上面代码的对比,发现mini-async-dva最大的特色就是model的effect支持async/await语法,路由组件默认就是异步导入,没必要再使用dynamic进行包裹了,固然还有视图层与model的绑定,也作了一点小优化,代码事后,就开始分析一下轮子咋实现的吧。github
咱们这个轮子仍是沿用redux做为状态管理,可是因为须要动态注册model对象,所以须要手动接管reducer里面的逻辑,好比当/foo
路由第一次激活时,Foo组件的model对象须要挂载到全局store里面去,那么经过发送一个type为@@redux/register
的action,在reducer里面手动挂载model对应的state对象,同时要将effects里面的方法都缓存起来,便于后续执行,咱们代码里是保存在effectsMap中。npm
const effectsMap = {}; const store = createStore((state, action) =>; { const { type, payload = {} } = action; const { namespace, effects, initalState, updateState } = payload; if (type === '@@redux/register') { // 注册 effectsMap[namespace] = effects; return Object.assign({}, state, { [namespace]: initalState }); } if (type === '@@redux/update') { // 反作用执行完毕,须要更新namespace对应的状态值 return Object.assign({}, state, { [namespace]: Object.assign({}, state[namespace], updateState) }); } if (type.includes('/') && !type.includes('@@redux/INIT')) { // 视图层发起的dispatch方法进入到这里,须要分离出namespace和具体的effect方法名 const [ sliceNameSpace, effect ] = type.split('/'); if (effectsMap[sliceNameSpace] && effectsMap[sliceNameSpace][effect]) { executeAsyncTask(state, sliceNameSpace, effectsMap[sliceNameSpace][effect], payload); // 执行异步任务 } } return state; }, {});
结合注释应该不难理解,接下来就看一下executeAsyncTask的实现吧,其实很简单:redux
function updateStore(namespace) { return function(state) { Promise.resolve().then(() => { store.dispatch({ type: '@@redux/update', payload: { namespace, updateState: state, } }); }); } } async function executeAsyncTask(state, namespace, fn, payload) { const response = await fn.call(state[namespace], payload, updateStore(namespace)); store.dispatch({ type: '@@redux/update', // 发起更新state的意图 payload: { namespace, updateState: response, } }); }
至此store就完成了动态注册和状态更新的基本需求,下面要实现组件的异步加载了。api
在mini-async-dva中,视图是异步加载的,这里的异步主要是控制视图依赖的models实现异步加载和注册,视图须要等到models完成注册后才能渲染,保证组件内部逻辑与store的状态保持同步。缓存
import { useStore } from 'react-redux'; function AsyncComponent({ deps, children, ...rest }) { const store = useStore(); const [modelLoaded, setModelLoaded] = useState(!Array.isArray(deps) && deps.length === 0); useEffect(() => { if(!modelLoaded) { Promise.all(deps.map((dep) => runImportTask(dep))).then(() => { setModelLoaded(true); }); } }, []); function runImportTask(dep) { if (!store.getState().hasOwnProperty(dep)) { // model没有注册过 return new Promise((resolve, reject) => { import(`models/${dep}.js`).then((module) => { const { namespace, state: initalState = {}, effects } = module.default; store.dispatch({ type: '@@redux/register', payload: { effects, initalState, namespace: namespace || dep, } }); resolve(); }).catch(reject); }); } } if (modelLoaded) { return ( <> {React.createElement(children, rest)} </> ); } return null; }
AsyncComponent组件主要的功能包含两点,其一是异步加载全部依赖的models,而后发起一个动态注册model对象的意图,其二是当models都加载完毕,渲染咱们的视图。前端工程师
function model(...deps) { return function wrapComponent(target) { const cacheRender = connect(function mapStateToProps(state) { return deps.reduce((mapState, dep) => { mapState[dep] = state[dep]; return mapState; }, {}); }, null)(target); return (props) => { return ( <AsyncComponent deps={deps} {...props}> {cacheRender} </AsyncComponent> ) }; } }
model函数搜集咱们的视图组件依赖的model名称,而后将视图组件包裹在AsyncComponent内,从而实现动态控制和connect的绑定,至此就基本完成了mini-async-dva的核心功能了。app
到这里本文也就结尾了,mini-async-dva的项目代码已经放到github上了,具体地址可查看附录【3】,若是看官以为能够,顺手点个小星星呗。
附录:
【1】https://github.com/dvajs/dva/issues/1919 (async支持讨论)
【2】https://mp.weixin.qq.com/s/frSXO79aq_BHg09rS-xHXA (一文完全搞懂 DvaJS 原理)
【3】https://github.com/lanpangzi-zkg/mini-async-dva (mini-async-dva)