前言
在使用antd pro的時候發現此框架的loading是使用dva自帶的dva-loading。之前分析源碼的時候有看見幾個附帶工具,但沒有去研究下。今天好奇下dva-loading的實現。
開始
先從用法了解
loading是針對effects的,所以我們所有需要顯示loading的異步操作都需要寫在effects中才可以體現dva-loading的方便性,下面例子中,loading是針對namespace為form的model的submitStepForm的effect,
import createLoading from 'dva-loading';
...
app.use(createLoading());
...
@connect(({ form, loading }) => ({
submitting: loading.effects['form/submitStepForm'],
data: form.step,
}))
app.use是什么,從源碼里知道,就是plugin.use,注冊對應的插件鉤子的。所以createLoading返回的是
return {
extraReducers,
onEffect,
};
extraReducers:加一個state,默認名字是loading,可以createLoading時傳入{namespace:xxx}
去覆蓋。
onEffect:是為了包裝所有的effect,在執行前后發出供上面reducers接收的action。
所以先看前置onEffect
- 前面的if的條件是外部傳入的,可用此進行優化,不然所有的effect都被重包裝
- 目標effect執行前,發出SHOW類型的action,結束后發出HIDE
- payload是model的namespace及effect的名字(在之前收集的時候,已經給所有的effect加上namespace前綴)。上面的例子就是
{namespace:'form',actionType :'form/submitStepForm'}
function onEffect(effect, { put }, model, actionType) {
const { namespace } = model;
if (
(only.length === 0 && except.length === 0)
|| (only.length > 0 && only.indexOf(actionType) !== -1)
|| (except.length > 0 && except.indexOf(actionType) === -1)
) {
return function*(...args) {
yield put({ type: SHOW, payload: { namespace, actionType } });
yield effect(...args);
yield put({ type: HIDE, payload: { namespace, actionType } });
};
} else {
return effect;
}
}
到 extraReducers
- state說明,從內到外,effects:可知道有哪些effect在loading,models:可知道有哪些model在loading,gobal有沒有在的loading的effect。可以知道后面兩個可以從第一個推理得出,但dva幫我們算出來,有這兩個確認會方便,比如一個功能的數據由一個model(kk)控制,這就可以不管有多少effect,都可以從
loading.models['kk']
中獲得該功能的loading狀態。 - 當接收到SHOW的action,會:保存payload里的namespace與actionType,并設為true,表示namespace的model和actionType的effect在加載,global一定為true,有在進行effect
- 當接收到HIDE的action,會: 把這個payload的effect名稱actionType設為false,表示該effect加載完,如果effects中找到有namespace前綴的在loading的effect,則該model也在loading中,如果models中找到有model在loading,則該全局loading中
const initialState = {
gobal: false, // 有沒有在的loading的effect
models: {}, // 可知道有哪些model在loading
effects: {}, // 可知道有哪些effect在loading
};
const extraReducers = {
[namespace](state = initialState, { type, payload }) {
const { namespace, actionType } = payload || {};
let ret;
switch (type) {
// 保存payload里的namespace與actionType,并設為true,表示namespace的model和actionType的effect在加載,
// global一定為true,有在進行effect
case SHOW:
ret = {
...state,
global: true,
models: { ...state.models, [namespace]: true },
effects: { ...state.effects, [actionType]: true },
};
break;
case HIDE: // eslint-disable-line
// 把這個payload的effect名稱actionType設為false,表示該effect加載完
const effects = { ...state.effects, [actionType]: false };
// 如果effects中找到有namespace前綴的在loading的effect,則該model也在loading中
const models = {
...state.models,
[namespace]: Object.keys(effects).some((actionType) => {
const _namespace = actionType.split('/')[0];
if (_namespace !== namespace) return false;
return effects[actionType];
}),
};
// 如果models中找到有model在loading,則該全局loading中
const global = Object.keys(models).some((namespace) => {
return models[namespace];
});
ret = {
...state,
global,
models,
effects,
};
break;
default:
ret = state;
break;
}
return ret;
},
};