前言
在使用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;
},
};