你是否还在使用以下代码绑定请求的loading状态?
try {
this.loading = true;
let res = await fetch("https://api.com/xxxx")
console.log(res)
this.loading = false;
} catch (err) {
this.loading = false;
}
总感觉这样写有点麻烦,而且,不优雅。所以得想办法弄个 mixin 来包装一下请求,自动绑定请求的状态,而且还应该能够绑定深层的状态。比如 loginForm.loading
。
先创建一个 mixin 吧。
reqStateMixin.js
export const LoadingMixin = {
methods: {}
};
因为核心功能就是将请求的状态设置到 data 中,所以第一步先搞定 setData 函数,同时支持深层数据。
如果要支持深层的数据,key 就可能是 loginForm.state.loading
这样的,那么我们就可以通过 path.split(".")
进行分割,然后一层一层取 Object,再在 Object 中设置 loading 状态。
有了思路就开始写代码,在 mixin 中新增一个函数
reqStateMixin.js
export const LoadingMixin = {
methods: {
_setDeepData(dataKey, data) {
let dPath = dataKey.split(".");
let rootObj = this.$data;
dPath.forEach((key, index) => {
if (index === dPath.length - 1) {
// 如果这是最后一层了,则将 data 设置到 object 中
this.$set(rootObj, key, data)
} else {
// 如果当前这一层不是最后一层,则表示这是一个 Object,取出来存好
rootObj = rootObj[key];
// TODO 不过也有可能取出来的是空,这里需要抛出一个错误
}
});
}
}
};
这样子就可以绑定到 data 中任意深度的数据了。
接下来再增加一个函数来包装请求,并返回一个包装后的请求。
export const LoadingMixin = {
methods: {
// _setDeepData(dataKey, data){...}
wrapPromising(promise, key = "loading") {
this._setDeepData(key, true);
return promise.finally(() => {
this._setDeepData(key, false);
});
},
}
}
这样子在其他地方调用的时候就可以很优雅了,像这样
new Vue({
mixins: [ LoadingMixin ],
template: `<div>loading: {{loading}}</div> <div>deepLoading: {{a.b.c}}</div>`,
data() {
return {
loading: false,
a: {
b: {
c: false
}
}
}
},
methods: {
loadData() {
this.wrapPromising(fetch("/api/xxxx"), "loading")
.then(res => console.log(res))
this.wrapPromising(fetch("/api/abc"), "a.b.c")
.then(res => console.log(res))
}
}
})
当然,都到了这一步了,如果想绑定 Promise 的整个状态都是可以的。只需要再添加一点细节就好了。像这样:
export const PmStateMap = {
idle: "idle",
pending: "pending",
resolve: "resolve",
reject: "reject"
};
/// promise状态绑定混合类
export const PmStateMixin = {
methods: {
/**
* 包装 promise,将 promise 状态绑定到 data 中
* @param promise promise对象
* @param key 要绑定的 data key。与 vue watch 类似,支持深度绑定,如 "form.loading"
* @returns Promise
*/
wrapPromiseState(promise, key) {
console.assert(key !== "" && key != null, "key的值不能为空");
this._setDeepData(key, PmStateMap.pending);
return promise.then((res) => {
this._setDeepData(key, PmStateMap.resolve);
return res;
}).catch(error => {
console.error(error);
this._setDeepData(key, PmStateMap.reject);
return Promise.reject(error);
});
},
/**
* 包装 promise,将进行中状态绑定到 data 中
* @param promise
* @param key 要绑定的 data key。与 vue watch 类似,支持深度绑定,如 "form.loading"
* @returns Promise
*/
wrapPromising(promise, key = "loading") {
this._setDeepData(key, true);
return promise.finally(() => {
this._setDeepData(key, false);
});
},
_setDeepData(dataKey, data) {
let dPath = dataKey.split(".");
let rootObj = this.$data;
const notUndefined = (obj, index) => {
if (obj === undefined) {
const joined = dPath.slice(0, index).join(".");
const msg = `vm.$data.${dataKey} 中的 ${joined} 不存在 ${key}`;
throw new Error(msg);
}
};
const beObject = (obj, index) => {
if (typeof rootObj !== "object") {
const joined = dPath.slice(0, index + 1).join(".");
const msg = `vm.$data.${dataKey} 中的 ${joined} 不为 Object`
throw new Error(msg);
}
};
dPath.forEach((subKey, index) => {
if (index === dPath.length - 1) {
this.$set(rootObj, subKey, data);
} else {
rootObj = rootObj[subKey];
notUndefined(rootObj, index);
beObject(rootObj, index)
}
});
}
},
computed: {
pmStateMap() {
return PmStateMap;
}
}
};