前言
在项目中,如何管理loading
是一个很头疼的方式,有的是在请求封装里去做了一个全屏loading
,有的是在单页面中管理一个loading
,比如如下:
data(){
return{
loading:false
}
},
methods:{
async change(){
this.loading = true
// 执行异步操作
await asyncAction()
this.loading = false
}
}
如果管理的是一个还好,但是如果页面上存在着多个异步操作,维护起来简直要命。
之前有使用过dva
这个基于react-saga
的数据流框架,他有一个插件dva-loading
非常的好用,但由于是React
框架上的。一直想着如何借鉴其思路实现一个基于vuex
的loading
插件,是非常想做的一件事。
好在,[email protected]
中新增了一个subscribeAction
这个方法。
store.subscribeAction({
before: (action, state) => {
console.log(`before action ${action.type}`)
},
after: (action, state) => {
console.log(`after action ${action.type}`)
}
})
简单来说就是我们在发起的每一个action
都可以在这个方法中订阅到,before
和after
两个过程。心里一想,这岂不是跟dva-loading
有一样的一个动作吗。
如何实现
先看一下dva-loading
的源码,分析一下:
为了方便描述,直接贴源码补充注释,具体dva
的使用与vuex
的异同,可以看我之前写的那篇如何使用dva与服务端进行数据交互
const SHOW = '@@DVA_LOADING/SHOW'; // 定义reducer 类似于mutation
const HIDE = '@@DVA_LOADING/HIDE';
const NAMESPACE = 'loading'; // 定义命名空间
function createLoading(opts = {}) {
// 获取参数中的命名空间或者本地的名称
const namespace = opts.namespace || NAMESPACE;
const { only = [], except = [] } = opts;
if (only.length > 0 && except.length > 0) {
throw Error('It is ambiguous to configurate `only` and `except` items at the same time.');
}
// 初始化state
const initialState = {
global: false, // 定义全局 loading
models: {}, // 定义模块
effects: {}, // 定义effect 异步操作
};
const extraReducers = {
[namespace](state = initialState, { type, payload }) {
const { namespace, actionType } = payload || {};
let ret;
switch (type) {
// 如果是显示的话 将全局loading置为true
// 并且将本model中所有的loading都置为 true
case SHOW:
ret = {
...state,
global: true,
models: { ...state.models, [namespace]: true },
effects: { ...state.effects, [actionType]: true },
};
break;
// 如果是显示的话 将本模块的都置为false
case HIDE: {
const effects = { ...state.effects, [actionType]: false };
// 遍历操作将所有的都置为false
const models = {
...state.models,
[namespace]: Object.keys(effects).some(actionType => {
const _namespace = actionType.split('/')[0];
if (_namespace !== namespace) return false;
return effects[actionType];
}),
};
// 遍历所有的model 将所有命名空间中的都置为false
const global = Object.keys(models).some(namespace => {
return models[namespace];
});
ret = {
...state,
global,
models,
effects,
};
break;
}
default:
ret = state;
break;
}
// 最后返回到state中 成功修改了store state中的数据
return ret;
},
};
// 发起异步操作
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) {
// 用了generator做异步处理
// 发起一个同步操作 传入命名空间与异步操作type
yield put({ type: SHOW, payload: { namespace, actionType } });
// 具体执行的异步操作
yield effect(...args);
yield put({ type: HIDE, payload: { namespace, actionType } });
};
} else {
return effect;
}
}
return {
extraReducers,
onEffect,
};
}
export default createLoading;
模仿dva-loading
开始动手实现
const NAMESPACE = "loading"; // 定义模块名
const SHOW = "@@LOADING/SHOW" // 显示mutation 同步type
const HIDE = "@@LOADING/HIDE"
const createLoadingPlugin = ({
namespace = NAMESPACE,
includes = [],
excludes = []
} = {}) => {
return store => {
if (store.state[namespace]) {
throw new Error(
`createLoadingPlugin: ${namespace} exited in current store`
);
}
// new vuex的时候注册一个模块进去
store.registerModule(namespace, {
namespaced: true,
state: {
global: false, // 定义全局loading
effects: {}
},
// 同步方法
mutations: {
SHOW(state, { payload }) {
state.global = true;
state.effects = {
...state.effects,
[payload]: true // 将当前的action 置为true
};
},
HIDE(state, { payload }) {
state.global = false;
state.effects = {
...state.effects,
[payload]: false // 将当前的action 置为false
};
}
}
});
store.subscribeAction({
// 发起一个action 之前会走这里
before: action => {
console.log(`before action ${action.type}`);
if (onEffect(action, includes, excludes)) {
store.commit({ type: SHOW, payload: action.type });
}
},
// 发起一个action 之后会走这里
after: action => {
console.log(`after action ${action.type}`);
if (onEffect(action, includes, excludes)) {
store.commit({ type: HIDE, payload: action.type });
}
}
});
};
};
// 判断是否要执行
function onEffect({ type }, includes, excludes) {
if (includes.length === 0 && excludes.length === 0) {
return true;
}
if (includes.length > 0) {
return includes.indexOf(type) > -1;
}
return excludes.length > 0 && excludes.indexOf(type) === -1;
}
export default createLoadingPlugin;
使用
- 在dva中是这样使用的
@connect(({loading})=>({
loading:loading.effects['user/addAction']
}))
- vuex中是这样使用的
import { mapState } from "vuex";
computed: {
...mapState({
loading: state => state["@@LOADING"].effects["user/addAction"]
})
},
两者相同的都是在effects[]
这个数组中取key
为我们要执行的那个异步操作的type
这样就能获取到他的loading
状态
因为在两者的全局loading
模块中,有一个effects
对象数组,结构类似于这样
state = {
effects:[
{'user/addAction':true},
{'user/updateAction':false},
]
}
好了这下可以愉快的在页面中使用loading
了。对了差点忘记,还要去注册插件
import createLoadingPlugin from "@/utils/vuex-loading";
export default new Vuex.Store({
plugins: [createLoadingPlugin()],
state: {},
mutations: {},
actions: {},
modules
});
特别注意的是,因为是基于subscribeAction
这个方法实现的,所以需要将vuex
版本升级至3.1.0+
关于
- 借鉴dva-loading
- 本文首发于实现一个vuex-loading插件