useLoading
一个管理 loading 状态的工具。
执行 useLoading 返回:
- loading: 一个
ComputedRef<boolean>
类型的变量,代表当前的 loading 状态 - executor:执行器,用于执行需要 loading 的函数
- closeLoading:用于手动关闭 loading
import { computed, ref } from 'vue';
// 执行函数类型
type ExecFunc<P extends Array<any> = any[], T = any> = (...args: P) => Promise<T>;
// 执行函数参数
type ExecFuncParams<P extends Array<any> = any[]> = Parameters<ExecFunc<P>>;
/**
* loading 状态管理
* 自动管理函数执行的 loading 状态
*/
export function useLoading() {
// 记录当前 loading 的数量
const loadingCount = ref(0);
const loading = computed(() => loadingCount.value > 0);
const setLoading = (value: boolean) => {
loadingCount.value = value ? loadingCount.value + 1 : Math.max(loadingCount.value - 1, 0);
};
/**
* 执行器, 执行传入的函数, 并自动管理 loading 状态
*/
const executor = async <P extends Array<any> = any[], T = any>(
execFunc: ExecFunc<P, T>,
...args: ExecFuncParams<P>
): Promise<T> => {
setLoading(true);
try {
const result = await execFunc(...args);
return result;
} finally {
// 在 finally 中执行, 确保无论成功还是失败都会关闭 loading
setLoading(false);
}
};
/**
* 手动关闭所有 loading
*/
const closeLoading = () => {
loadingCount.value = 0;
};
return { loading, executor, closeLoading };
}
ExecFunc
是执行函数的类型,使用泛型 P 表示执行函数的参数,泛型 T 表示执行函数的返回值。
使用示例
script
const func = async (name: string, age: number) => {
await Promise.resolve();
return { name, age };
}
const { loading, executor, closeLoading } = useLoading();
const handleLoading = async () => {
const res = await executor(func, 'eno', 18);
console.log(res); // {name: 'eno', age: 18}
}
template
<template>
<div v-loading="loading" style="width: 100px; height: 100px"></div>
<a-button @click="handleLoading()">loading</a-button>
<a-button @click="closeLoading()">close loading</a-button>
</template>
为什么要使用 loadingCount 记录 loading 数量
useLoading 主要用于解决多次执行一个需要 loading 的函数时,先完成的函数会取消掉其他函数 loading 状态的问题
如果我们不用 loadingCount
function useLoading() {
const loading = ref(false);
// 执行器, 执行传入的函数, 并自动管理 loading 状态
const executor = async <P extends Array<any> = any[], T = any>(
execFunc: ExecFunc<P, T>,
...args: ExecFuncParams<P>
): Promise<T> => {
loading.value = true;
try {
const result = await execFunc(...args);
return result;
} finally {
loading.value = false;
}
};
// 手动关闭 loading
const closeLoading = () => {
loading.value = false;
};
return { loading, executor, closeLoading };
}
尝试执行两次执行 executor
函数,并且控制中间间隔一段时间
const func = async (name: string) => {
await new Promise(resolve => {
// 模拟 5 秒的延时异步操作
setTimeout(() => {
resolve('');
}, 5 * 1e3);
});
console.log(name);
};
const { loading, executor, closeLoading } = useLoading();
const handleLoading = async () => {
executor(func, 'eno');
// 4 秒后在第一次未完成时,再次执行 executor
setTimeout(() => {
executor(func, 'eno');
}, 4 * 1e3);
};
尝试后的效果是:点击 loading 按钮,显示 loading,五秒后第一个执行函数完成,loading 关闭,但是此时第二个还行函数还未完成,这样显然不是我们想要的效果。