dva的effect那麼難用,本身造一個輪子吧

背景

對於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

1.路由文件

## 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>
......

2.models

## 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();
        }
    }
};

3.view層

## 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

輪子實現

1.store管理

咱們這個輪子仍是沿用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

2.異步加載

在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都加載完畢,渲染咱們的視圖。前端工程師

3.狀態綁定

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)

福祿·研發中心 福袋
相關文章
相關標籤/搜索