Vuehook
很多react hook我们可以直接拿过来改造一下用,
这里就不赘述了,
我们来实现一个 vue 的 useRequest 数据请求 hook,
基本仿照ahooks的配置和功能实现。
import { onMounted, onUnmounted, ref, watch, watchEffect } from 'vue';
import { ElMessage } from 'element-plus';
import _ from 'lodash';
import { useBoolean, useFuncDebounce, useThrottle } from '.';
import { cleanObject } from '@/tools';
interface OptionsConfig {
loop?: number;
debounceWait?: number;
throttleWait?: number;
cacheKey?: string;
ready?: boolean;
loadingDelay?: number;
refreshOnWindowFocus?: boolean;
refreshDeps?: unknown[];
retryNum?: number;
manual?: boolean;
responsePath?: string;
}
interface EndConfig {
success?: (res: unknown) => void;
error?: (error: Error) => void;
}
const RESPONSRCODE = 200;
const CODEPATH = 'data.code';
const FAILEDMESSAGE = '获取数据失败';
const ENDCONFIG = {
success: (res: unknown) => {},
error: (error: Error) => {},
};
export const useRequest = (
syncFunc: (config?: any) => Promise<unknown>,
options: OptionsConfig = {},
end: EndConfig = ENDCONFIG
) => {
const {
loop = 0,
ready = true,
retryNum = 0,
cacheKey = '',
manual = false,
refreshDeps = [],
debounceWait = 0,
throttleWait = 0,
loadingDelay = 0,
responsePath = '',
refreshOnWindowFocus = false,
} = options;
const throttleCallback = useThrottle();
const debouncedCallback = useFuncDebounce();
const [loading, ___, loadingOn, loadingOff] = useBoolean();
const data = ref<unknown>({});
const retryNumRef = ref<number>(0);
const requestConfig = ref<unknown>({});
refreshDeps.forEach(ele => {
watch(
() => ele,
() => {
console.warn('useRequest refreshDeps element is change!', ele);
getSyncDataWrap(requestConfig.value);
},
{
deep: true,
}
);
});
const retry = (config?: any) => {
if (!retryNum) return;
if (retryNumRef.value < retryNum) {
retryNumRef.value += 1;
getSyncData(config);
}
};
const run = (config?: unknown) => {
getSyncDataWrap(config);
};
const saveData = (res: unknown) => {
if (responsePath) {
data.value = _.get(res, responsePath, {}) || {};
} else {
data.value = res;
}
};
const getSyncData = (config?: unknown) => {
console.warn('useRequest getSyncData config', config);
try {
loadingOn();
if (ready) {
if (cacheKey) {
const locationCacheData = JSON.parse(localStorage.getItem(cacheKey) || '{}');
if (!_.isEmpty(locationCacheData)) {
data.value = locationCacheData;
saveData(locationCacheData);
end.success && end.success(locationCacheData);
loadingOff();
}
} else {
if (!_.isEmpty(config)) {
requestConfig.value = config;
}
syncFunc(cleanObject(config as { [key: string]: unknown }))
.then(res => {
if (_.get(res, CODEPATH) === RESPONSRCODE) {
saveData(res);
end.success && end.success(res);
cacheKey && localStorage.setItem(cacheKey, JSON.stringify(res));
loadingOff();
} else {
ElMessage.error(FAILEDMESSAGE);
loading.value = false;
Promise.reject(new Error(FAILEDMESSAGE));
}
})
.catch(error => {
loading.value = false;
end.error && end.error(error);
console.log('useRequest error catch!', error);
retry(config);
});
}
}
} catch (error) {
console.log(error);
loadingOff();
}
};
const getSyncDataWrap = (config?: unknown) => {
if (debounceWait) {
return debouncedCallback(getSyncData, debounceWait)(config);
}
if (throttleWait) {
return throttleCallback(getSyncData, throttleWait)(config);
}
return getSyncData(config);
};
const loadingDelatyTimer = ref<NodeJS.Timeout | undefined>(undefined);
onMounted(() => {
if (manual) {
return;
}
if (loadingDelay) {
loadingDelatyTimer.value = setTimeout(() => {
getSyncDataWrap(requestConfig.value);
}, loadingDelay);
return;
}
getSyncDataWrap(requestConfig.value);
});
onUnmounted(() => {
if (loadingDelatyTimer.value) {
clearTimeout(loadingDelatyTimer.value);
}
});
const timer = ref<NodeJS.Timeout | undefined>(undefined);
const loopFunc = () => {
console.warn('useRequest loop is start!', loop);
timer.value = setTimeout(() => {
loopFunc();
getSyncDataWrap(requestConfig.value);
}, loop);
};
watchEffect(() => {
if (loop) {
loopFunc();
}
if (!loop && timer.value) {
clearTimeout(timer.value);
}
});
onUnmounted(() => {
if (timer.value) clearTimeout(timer.value);
});
const windowFocusFunc = () => {
if (document.visibilityState === 'visible' && refreshOnWindowFocus) {
debouncedCallback(run, 5000)(requestConfig.value);
}
};
onMounted(() => {
document.addEventListener('visibilitychange', windowFocusFunc);
});
onUnmounted(() => {
document.removeEventListener('visibilitychange', windowFocusFunc);
});
return {
data,
loading,
run,
};
};
const data = reactive<unkonw[]>([]);
const { data, run, loading } = useRequest(evaluateList, {}, {
success(res) {
data.length = 0;
total.value = _.get(res, 'data.data.total', 0);
data.push(...(_.get(res, 'data.data.records', []) || []))
},
})
<ContentContainer v-loading.lock="loading">
...
</ContentContainer>
<ContentContainer
v-loading={loading.value}
v-slots={{
default: () => (
<div class="{styles.wrap}">
{renderHeader()}
</div>
),
}}
/>