remove 国际化删除, 依赖精简

This commit is contained in:
bootx
2024-07-02 21:27:05 +08:00
parent e1b2d4c094
commit 4835eb66c7
188 changed files with 2086 additions and 1161 deletions

View File

@@ -1,4 +1,4 @@
import { defineBuildConfig } from 'unbuild';
import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
clean: true,
@@ -7,4 +7,4 @@ export default defineBuildConfig({
rollup: {
emitCJS: true,
},
});
})

View File

@@ -1,7 +1,7 @@
export * from './onMountedOrActivated';
export * from './useAttrs';
export * from './useRefs';
export * from './useRequest';
export * from './useScrollTo';
export * from './useWindowSizeFn';
export { useTimeoutFn } from '@vueuse/core';
export * from './onMountedOrActivated'
export * from './useAttrs'
export * from './useRefs'
export * from './useRequest'
export * from './useScrollTo'
export * from './useWindowSizeFn'
export { useTimeoutFn } from '@vueuse/core'

View File

@@ -1,25 +1,25 @@
import { type AnyFunction } from '@vben/types';
import { nextTick, onActivated, onMounted } from 'vue';
import { type AnyFunction } from '@vben/types'
import { nextTick, onActivated, onMounted } from 'vue'
/**
* 在 OnMounted 或者 OnActivated 时触发
* @param hook 任何函数(包括异步函数)
*/
function onMountedOrActivated(hook: AnyFunction) {
let mounted: boolean;
let mounted: boolean
onMounted(() => {
hook();
hook()
nextTick(() => {
mounted = true;
});
});
mounted = true
})
})
onActivated(() => {
if (mounted) {
hook();
hook()
}
});
})
}
export { onMountedOrActivated };
export { onMountedOrActivated }

View File

@@ -1,43 +1,43 @@
import { type Recordable } from '@vben/types';
import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue';
import { type Recordable } from '@vben/types'
import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue'
interface UseAttrsOptions {
excludeListeners?: boolean;
excludeKeys?: string[];
excludeDefaultKeys?: boolean;
excludeListeners?: boolean
excludeKeys?: string[]
excludeDefaultKeys?: boolean
}
const DEFAULT_EXCLUDE_KEYS = ['class', 'style'];
const LISTENER_PREFIX = /^on[A-Z]/;
const DEFAULT_EXCLUDE_KEYS = ['class', 'style']
const LISTENER_PREFIX = /^on[A-Z]/
function entries<T>(obj: Recordable<T>): [string, T][] {
return Object.keys(obj).map((key: string) => [key, obj[key]]);
return Object.keys(obj).map((key: string) => [key, obj[key]])
}
function useAttrs(options: UseAttrsOptions = {}): Recordable<any> {
const instance = getCurrentInstance();
if (!instance) return {};
const instance = getCurrentInstance()
if (!instance) return {}
const { excludeListeners = false, excludeKeys = [], excludeDefaultKeys = true } = options;
const attrs = shallowRef({});
const allExcludeKeys = excludeKeys.concat(excludeDefaultKeys ? DEFAULT_EXCLUDE_KEYS : []);
const { excludeListeners = false, excludeKeys = [], excludeDefaultKeys = true } = options
const attrs = shallowRef({})
const allExcludeKeys = excludeKeys.concat(excludeDefaultKeys ? DEFAULT_EXCLUDE_KEYS : [])
// Since attrs are not reactive, make it reactive instead of doing in `onUpdated` hook for better performance
instance.attrs = reactive(instance.attrs);
instance.attrs = reactive(instance.attrs)
watchEffect(() => {
const res = entries(instance.attrs).reduce((acm, [key, val]) => {
if (!allExcludeKeys.includes(key) && !(excludeListeners && LISTENER_PREFIX.test(key))) {
acm[key] = val;
acm[key] = val
}
return acm;
}, {} as Recordable<any>);
return acm
}, {} as Recordable<any>)
attrs.value = res;
});
attrs.value = res
})
return attrs;
return attrs
}
export { useAttrs, type UseAttrsOptions };
export { useAttrs, type UseAttrsOptions }

View File

@@ -1,24 +1,24 @@
import type { ComponentPublicInstance, Ref } from 'vue';
import { onBeforeUpdate, shallowRef } from 'vue';
import type { ComponentPublicInstance, Ref } from 'vue'
import { onBeforeUpdate, shallowRef } from 'vue'
function useRefs<T = HTMLElement>(): {
refs: Ref<T[]>;
setRefs: (index: number) => (el: Element | ComponentPublicInstance | null) => void;
refs: Ref<T[]>
setRefs: (index: number) => (el: Element | ComponentPublicInstance | null) => void
} {
const refs = shallowRef([]) as Ref<T[]>;
const refs = shallowRef([]) as Ref<T[]>
onBeforeUpdate(() => {
refs.value = [];
});
refs.value = []
})
const setRefs = (index: number) => (el: Element | ComponentPublicInstance | null) => {
refs.value[index] = el as T;
};
refs.value[index] = el as T
}
return {
refs,
setRefs,
};
}
}
export { useRefs };
export { useRefs }

View File

@@ -1,20 +1,20 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { reactive } from 'vue';
import { reactive } from 'vue'
import type { FetchState, PluginReturn, Service, Subscribe, UseRequestOptions } from './types';
import { isFunction } from './utils/isFunction';
import type { FetchState, PluginReturn, Service, Subscribe, UseRequestOptions } from './types'
import { isFunction } from './utils/isFunction'
export default class Fetch<TData, TParams extends any[]> {
pluginImpls: PluginReturn<TData, TParams>[] = [];
pluginImpls: PluginReturn<TData, TParams>[] = []
count: number = 0;
count: number = 0
state: FetchState<TData, TParams> = reactive({
loading: false,
params: undefined,
data: undefined,
error: undefined,
});
})
constructor(
public serviceRef: Service<TData, TParams>,
@@ -22,126 +22,126 @@ export default class Fetch<TData, TParams extends any[]> {
public subscribe: Subscribe,
public initState: Partial<FetchState<TData, TParams>> = {},
) {
this.setState({ loading: !options.manual, ...initState });
this.setState({ loading: !options.manual, ...initState })
}
setState(s: Partial<FetchState<TData, TParams>> = {}) {
Object.assign(this.state, s);
this.subscribe();
Object.assign(this.state, s)
this.subscribe()
}
runPluginHandler(event: keyof PluginReturn<TData, TParams>, ...rest: any[]) {
// @ts-ignore
const r = this.pluginImpls.map((i) => i[event]?.(...rest)).filter(Boolean);
return Object.assign({}, ...r);
const r = this.pluginImpls.map((i) => i[event]?.(...rest)).filter(Boolean)
return Object.assign({}, ...r)
}
async runAsync(...params: TParams): Promise<TData> {
this.count += 1;
const currentCount = this.count;
this.count += 1
const currentCount = this.count
const {
stopNow = false,
returnNow = false,
...state
} = this.runPluginHandler('onBefore', params);
} = this.runPluginHandler('onBefore', params)
// stop request
if (stopNow) {
return new Promise(() => {});
return new Promise(() => {})
}
this.setState({
loading: true,
params,
...state,
});
})
// return now
if (returnNow) {
return Promise.resolve(state.data);
return Promise.resolve(state.data)
}
this.options.onBefore?.(params);
this.options.onBefore?.(params)
try {
// replace service
let { servicePromise } = this.runPluginHandler('onRequest', this.serviceRef, params);
let { servicePromise } = this.runPluginHandler('onRequest', this.serviceRef, params)
if (!servicePromise) {
servicePromise = this.serviceRef(...params);
servicePromise = this.serviceRef(...params)
}
const res = await servicePromise;
const res = await servicePromise
if (currentCount !== this.count) {
// prevent run.then when request is canceled
return new Promise(() => {});
return new Promise(() => {})
}
// const formattedResult = this.options.formatResultRef.current ? this.options.formatResultRef.current(res) : res;
this.setState({ data: res, error: undefined, loading: false });
this.setState({ data: res, error: undefined, loading: false })
this.options.onSuccess?.(res, params);
this.runPluginHandler('onSuccess', res, params);
this.options.onSuccess?.(res, params)
this.runPluginHandler('onSuccess', res, params)
this.options.onFinally?.(params, res, undefined);
this.options.onFinally?.(params, res, undefined)
if (currentCount === this.count) {
this.runPluginHandler('onFinally', params, res, undefined);
this.runPluginHandler('onFinally', params, res, undefined)
}
return res;
return res
} catch (error) {
if (currentCount !== this.count) {
// prevent run.then when request is canceled
return new Promise(() => {});
return new Promise(() => {})
}
this.setState({ error, loading: false });
this.setState({ error, loading: false })
this.options.onError?.(error, params);
this.runPluginHandler('onError', error, params);
this.options.onError?.(error, params)
this.runPluginHandler('onError', error, params)
this.options.onFinally?.(params, undefined, error);
this.options.onFinally?.(params, undefined, error)
if (currentCount === this.count) {
this.runPluginHandler('onFinally', params, undefined, error);
this.runPluginHandler('onFinally', params, undefined, error)
}
throw error;
throw error
}
}
run(...params: TParams) {
this.runAsync(...params).catch((error) => {
if (!this.options.onError) {
console.error(error);
console.error(error)
}
});
})
}
cancel() {
this.count += 1;
this.setState({ loading: false });
this.count += 1
this.setState({ loading: false })
this.runPluginHandler('onCancel');
this.runPluginHandler('onCancel')
}
refresh() {
// @ts-ignore
this.run(...(this.state.params || []));
this.run(...(this.state.params || []))
}
refreshAsync() {
// @ts-ignore
return this.runAsync(...(this.state.params || []));
return this.runAsync(...(this.state.params || []))
}
mutate(data?: TData | ((oldData?: TData) => TData | undefined)) {
const targetData = isFunction(data) ? data(this.state.data) : data;
this.runPluginHandler('onMutate', targetData);
this.setState({ data: targetData });
const targetData = isFunction(data) ? data(this.state.data) : data
this.runPluginHandler('onMutate', targetData)
this.setState({ data: targetData })
}
}

View File

@@ -1,15 +1,15 @@
import useAutoRunPlugin from './plugins/useAutoRunPlugin';
import useCachePlugin from './plugins/useCachePlugin';
import useDebouncePlugin from './plugins/useDebouncePlugin';
import useLoadingDelayPlugin from './plugins/useLoadingDelayPlugin';
import usePollingPlugin from './plugins/usePollingPlugin';
import useRefreshOnWindowFocusPlugin from './plugins/useRefreshOnWindowFocusPlugin';
import useRetryPlugin from './plugins/useRetryPlugin';
import useThrottlePlugin from './plugins/useThrottlePlugin';
import type { Service, UseRequestOptions, UseRequestPlugin } from './types';
import { useRequestImplement } from './useRequestImplement';
import useAutoRunPlugin from './plugins/useAutoRunPlugin'
import useCachePlugin from './plugins/useCachePlugin'
import useDebouncePlugin from './plugins/useDebouncePlugin'
import useLoadingDelayPlugin from './plugins/useLoadingDelayPlugin'
import usePollingPlugin from './plugins/usePollingPlugin'
import useRefreshOnWindowFocusPlugin from './plugins/useRefreshOnWindowFocusPlugin'
import useRetryPlugin from './plugins/useRetryPlugin'
import useThrottlePlugin from './plugins/useThrottlePlugin'
import type { Service, UseRequestOptions, UseRequestPlugin } from './types'
import { useRequestImplement } from './useRequestImplement'
export { clearCache } from './utils/cache';
export { clearCache } from './utils/cache'
export function useRequest<TData, TParams extends any[]>(
service: Service<TData, TParams>,
@@ -26,5 +26,5 @@ export function useRequest<TData, TParams extends any[]>(
useAutoRunPlugin,
useCachePlugin,
useRetryPlugin,
] as UseRequestPlugin<TData, TParams>[]);
] as UseRequestPlugin<TData, TParams>[])
}

View File

@@ -1,52 +1,52 @@
import { ref, unref, watch } from 'vue';
import { ref, unref, watch } from 'vue'
import type { UseRequestPlugin } from '../types';
import type { UseRequestPlugin } from '../types'
// support refreshDeps & ready
const useAutoRunPlugin: UseRequestPlugin<any, any[]> = (
fetchInstance,
{ manual, ready = true, defaultParams = [], refreshDeps = [], refreshDepsAction },
) => {
const hasAutoRun = ref(false);
const hasAutoRun = ref(false)
watch(
() => unref(ready),
(readyVal) => {
if (!unref(manual) && readyVal) {
hasAutoRun.value = true;
fetchInstance.run(...defaultParams);
hasAutoRun.value = true
fetchInstance.run(...defaultParams)
}
},
);
)
if (refreshDeps.length) {
watch(refreshDeps, () => {
if (hasAutoRun.value) {
return;
return
}
if (!manual) {
if (refreshDepsAction) {
refreshDepsAction();
refreshDepsAction()
} else {
fetchInstance.refresh();
fetchInstance.refresh()
}
}
});
})
}
return {
onBefore: () => {
if (!unref(ready)) {
return { stopNow: true };
return { stopNow: true }
}
},
};
};
}
}
useAutoRunPlugin.onInit = ({ ready = true, manual }) => {
return {
loading: !unref(manual) && unref(ready),
};
};
}
}
export default useAutoRunPlugin;
export default useAutoRunPlugin

View File

@@ -1,10 +1,10 @@
import { onUnmounted, ref, watchEffect } from 'vue';
import { onUnmounted, ref, watchEffect } from 'vue'
import type { UseRequestPlugin } from '../types';
import type { CachedData } from '../utils/cache';
import { getCache, setCache } from '../utils/cache';
import { getCachePromise, setCachePromise } from '../utils/cachePromise';
import { subscribe, trigger } from '../utils/cacheSubscribe';
import type { UseRequestPlugin } from '../types'
import type { CachedData } from '../utils/cache'
import { getCache, setCache } from '../utils/cache'
import { getCachePromise, setCachePromise } from '../utils/cachePromise'
import { subscribe, trigger } from '../utils/cacheSubscribe'
const useCachePlugin: UseRequestPlugin<any, any[]> = (
fetchInstance,
@@ -16,52 +16,52 @@ const useCachePlugin: UseRequestPlugin<any, any[]> = (
getCache: customGetCache,
},
) => {
const unSubscribeRef = ref<() => void>();
const currentPromiseRef = ref<Promise<any>>();
const unSubscribeRef = ref<() => void>()
const currentPromiseRef = ref<Promise<any>>()
const _setCache = (key: string, cachedData: CachedData) => {
customSetCache ? customSetCache(cachedData) : setCache(key, cacheTime, cachedData);
trigger(key, cachedData.data);
};
customSetCache ? customSetCache(cachedData) : setCache(key, cacheTime, cachedData)
trigger(key, cachedData.data)
}
const _getCache = (key: string, params: any[] = []) => {
return customGetCache ? customGetCache(params) : getCache(key);
};
return customGetCache ? customGetCache(params) : getCache(key)
}
watchEffect(() => {
if (!cacheKey) return;
if (!cacheKey) return
// get data from cache when init
const cacheData = _getCache(cacheKey);
const cacheData = _getCache(cacheKey)
if (cacheData && Object.hasOwnProperty.call(cacheData, 'data')) {
fetchInstance.state.data = cacheData.data;
fetchInstance.state.params = cacheData.params;
fetchInstance.state.data = cacheData.data
fetchInstance.state.params = cacheData.params
if (staleTime === -1 || new Date().getTime() - cacheData.time <= staleTime) {
fetchInstance.state.loading = false;
fetchInstance.state.loading = false
}
}
// subscribe same cachekey update, trigger update
unSubscribeRef.value = subscribe(cacheKey, (data) => {
fetchInstance.setState({ data });
});
});
fetchInstance.setState({ data })
})
})
onUnmounted(() => {
unSubscribeRef.value?.();
});
unSubscribeRef.value?.()
})
if (!cacheKey) {
return {};
return {}
}
return {
onBefore: (params) => {
const cacheData = _getCache(cacheKey, params);
const cacheData = _getCache(cacheKey, params)
if (!cacheData || !Object.hasOwnProperty.call(cacheData, 'data')) {
return {};
return {}
}
// If the data is fresh, stop request
@@ -71,57 +71,57 @@ const useCachePlugin: UseRequestPlugin<any, any[]> = (
data: cacheData?.data,
error: undefined,
returnNow: true,
};
}
} else {
// If the data is stale, return data, and request continue
return { data: cacheData?.data, error: undefined };
return { data: cacheData?.data, error: undefined }
}
},
onRequest: (service, args) => {
let servicePromise = getCachePromise(cacheKey);
let servicePromise = getCachePromise(cacheKey)
// If has servicePromise, and is not trigger by self, then use it
if (servicePromise && servicePromise !== currentPromiseRef.value) {
return { servicePromise };
return { servicePromise }
}
servicePromise = service(...args);
currentPromiseRef.value = servicePromise;
setCachePromise(cacheKey, servicePromise);
servicePromise = service(...args)
currentPromiseRef.value = servicePromise
setCachePromise(cacheKey, servicePromise)
return { servicePromise };
return { servicePromise }
},
onSuccess: (data, params) => {
if (cacheKey) {
// cancel subscribe, avoid trgger self
unSubscribeRef.value?.();
unSubscribeRef.value?.()
_setCache(cacheKey, { data, params, time: new Date().getTime() });
_setCache(cacheKey, { data, params, time: new Date().getTime() })
// resubscribe
unSubscribeRef.value = subscribe(cacheKey, (d) => {
fetchInstance.setState({ data: d });
});
fetchInstance.setState({ data: d })
})
}
},
onMutate: (data) => {
if (cacheKey) {
// cancel subscribe, avoid trigger self
unSubscribeRef.value?.();
unSubscribeRef.value?.()
_setCache(cacheKey, {
data,
params: fetchInstance.state.params,
time: new Date().getTime(),
});
})
// resubscribe
unSubscribeRef.value = subscribe(cacheKey, (d) => {
fetchInstance.setState({ data: d });
});
fetchInstance.setState({ data: d })
})
}
},
};
};
}
}
export default useCachePlugin;
export default useCachePlugin

View File

@@ -1,42 +1,42 @@
import type { DebouncedFunc, DebounceSettings } from 'lodash-es';
import { debounce } from 'lodash-es';
import { computed, ref, watchEffect } from 'vue';
import type { DebouncedFunc, DebounceSettings } from 'lodash-es'
import { debounce } from 'lodash-es'
import { computed, ref, watchEffect } from 'vue'
import type { UseRequestPlugin } from '../types';
import type { UseRequestPlugin } from '../types'
const useDebouncePlugin: UseRequestPlugin<any, any[]> = (
fetchInstance,
{ debounceWait, debounceLeading, debounceTrailing, debounceMaxWait },
) => {
const debouncedRef = ref<DebouncedFunc<any>>();
const debouncedRef = ref<DebouncedFunc<any>>()
const options = computed(() => {
const ret: DebounceSettings = {};
const ret: DebounceSettings = {}
if (debounceLeading !== undefined) {
ret.leading = debounceLeading;
ret.leading = debounceLeading
}
if (debounceTrailing !== undefined) {
ret.trailing = debounceTrailing;
ret.trailing = debounceTrailing
}
if (debounceMaxWait !== undefined) {
ret.maxWait = debounceMaxWait;
ret.maxWait = debounceMaxWait
}
return ret;
});
return ret
})
watchEffect(() => {
if (debounceWait) {
const _originRunAsync = fetchInstance.runAsync.bind(fetchInstance);
const _originRunAsync = fetchInstance.runAsync.bind(fetchInstance)
debouncedRef.value = debounce(
(callback) => {
callback();
callback()
},
debounceWait,
options.value,
);
)
// debounce runAsync should be promise
// https://github.com/lodash/lodash/issues/4400#issuecomment-834800398
@@ -45,27 +45,27 @@ const useDebouncePlugin: UseRequestPlugin<any, any[]> = (
debouncedRef.value?.(() => {
_originRunAsync(...args)
.then(resolve)
.catch(reject);
});
});
};
.catch(reject)
})
})
}
return () => {
debouncedRef.value?.cancel();
fetchInstance.runAsync = _originRunAsync;
};
debouncedRef.value?.cancel()
fetchInstance.runAsync = _originRunAsync
}
}
});
})
if (!debounceWait) {
return {};
return {}
}
return {
onCancel: () => {
debouncedRef.value?.cancel();
debouncedRef.value?.cancel()
},
};
};
}
}
export default useDebouncePlugin;
export default useDebouncePlugin

View File

@@ -1,45 +1,45 @@
import { ref, unref } from 'vue';
import { ref, unref } from 'vue'
import type { UseRequestPlugin, UseRequestTimeout } from '../types';
import type { UseRequestPlugin, UseRequestTimeout } from '../types'
const useLoadingDelayPlugin: UseRequestPlugin<any, any[]> = (
fetchInstance,
{ loadingDelay, ready },
) => {
const timerRef = ref<UseRequestTimeout>();
const timerRef = ref<UseRequestTimeout>()
if (!loadingDelay) {
return {};
return {}
}
const cancelTimeout = () => {
if (timerRef.value) {
clearTimeout(timerRef.value);
clearTimeout(timerRef.value)
}
};
}
return {
onBefore: () => {
cancelTimeout();
cancelTimeout()
// Two cases:
// 1. ready === undefined
// 2. ready === true
if (unref(ready) !== false) {
timerRef.value = setTimeout(() => {
fetchInstance.setState({ loading: true });
}, loadingDelay);
fetchInstance.setState({ loading: true })
}, loadingDelay)
}
return { loading: false };
return { loading: false }
},
onFinally: () => {
cancelTimeout();
cancelTimeout()
},
onCancel: () => {
cancelTimeout();
cancelTimeout()
},
};
};
}
}
export default useLoadingDelayPlugin;
export default useLoadingDelayPlugin

View File

@@ -1,46 +1,46 @@
import { ref, watch } from 'vue';
import { ref, watch } from 'vue'
import type { UseRequestPlugin, UseRequestTimeout } from '../types';
import { isDocumentVisible } from '../utils/isDocumentVisible';
import subscribeReVisible from '../utils/subscribeReVisible';
import type { UseRequestPlugin, UseRequestTimeout } from '../types'
import { isDocumentVisible } from '../utils/isDocumentVisible'
import subscribeReVisible from '../utils/subscribeReVisible'
const usePollingPlugin: UseRequestPlugin<any, any[]> = (
fetchInstance,
{ pollingInterval, pollingWhenHidden = true, pollingErrorRetryCount = -1 },
) => {
const timerRef = ref<UseRequestTimeout>();
const unsubscribeRef = ref<() => void>();
const countRef = ref<number>(0);
const timerRef = ref<UseRequestTimeout>()
const unsubscribeRef = ref<() => void>()
const countRef = ref<number>(0)
const stopPolling = () => {
if (timerRef.value) {
clearTimeout(timerRef.value);
clearTimeout(timerRef.value)
}
unsubscribeRef.value?.();
};
unsubscribeRef.value?.()
}
watch(
() => pollingInterval,
() => {
if (!pollingInterval) {
stopPolling();
stopPolling()
}
},
);
)
if (!pollingInterval) {
return {};
return {}
}
return {
onBefore: () => {
stopPolling();
stopPolling()
},
onError: () => {
countRef.value += 1;
countRef.value += 1
},
onSuccess: () => {
countRef.value = 0;
countRef.value = 0
},
onFinally: () => {
if (
@@ -52,20 +52,20 @@ const usePollingPlugin: UseRequestPlugin<any, any[]> = (
// if pollingWhenHidden = false && document is hidden, then stop polling and subscribe revisible
if (!pollingWhenHidden && !isDocumentVisible()) {
unsubscribeRef.value = subscribeReVisible(() => {
fetchInstance.refresh();
});
fetchInstance.refresh()
})
} else {
fetchInstance.refresh();
fetchInstance.refresh()
}
}, pollingInterval);
}, pollingInterval)
} else {
countRef.value = 0;
countRef.value = 0
}
},
onCancel: () => {
stopPolling();
stopPolling()
},
};
};
}
}
export default usePollingPlugin;
export default usePollingPlugin

View File

@@ -1,37 +1,37 @@
import { onUnmounted, ref, watchEffect } from 'vue';
import { onUnmounted, ref, watchEffect } from 'vue'
import type { UseRequestPlugin } from '../types';
import { limit } from '../utils/limit';
import subscribeFocus from '../utils/subscribeFocus';
import type { UseRequestPlugin } from '../types'
import { limit } from '../utils/limit'
import subscribeFocus from '../utils/subscribeFocus'
const useRefreshOnWindowFocusPlugin: UseRequestPlugin<any, any[]> = (
fetchInstance,
{ refreshOnWindowFocus, focusTimespan = 5000 },
) => {
const unsubscribeRef = ref<() => void>();
const unsubscribeRef = ref<() => void>()
const stopSubscribe = () => {
unsubscribeRef.value?.();
};
unsubscribeRef.value?.()
}
watchEffect(() => {
if (refreshOnWindowFocus) {
const limitRefresh = limit(fetchInstance.refresh.bind(fetchInstance), focusTimespan);
const limitRefresh = limit(fetchInstance.refresh.bind(fetchInstance), focusTimespan)
unsubscribeRef.value = subscribeFocus(() => {
limitRefresh();
});
limitRefresh()
})
}
return () => {
stopSubscribe();
};
});
stopSubscribe()
}
})
onUnmounted(() => {
stopSubscribe();
});
stopSubscribe()
})
return {};
};
return {}
}
export default useRefreshOnWindowFocusPlugin;
export default useRefreshOnWindowFocusPlugin

View File

@@ -1,54 +1,54 @@
import { ref } from 'vue';
import { ref } from 'vue'
import type { UseRequestPlugin, UseRequestTimeout } from '../types';
import type { UseRequestPlugin, UseRequestTimeout } from '../types'
const useRetryPlugin: UseRequestPlugin<any, any[]> = (
fetchInstance,
{ retryInterval, retryCount },
) => {
const timerRef = ref<UseRequestTimeout>();
const countRef = ref(0);
const timerRef = ref<UseRequestTimeout>()
const countRef = ref(0)
const triggerByRetry = ref(false);
const triggerByRetry = ref(false)
if (!retryCount) {
return {};
return {}
}
return {
onBefore: () => {
if (!triggerByRetry.value) {
countRef.value = 0;
countRef.value = 0
}
triggerByRetry.value = false;
triggerByRetry.value = false
if (timerRef.value) {
clearTimeout(timerRef.value);
clearTimeout(timerRef.value)
}
},
onSuccess: () => {
countRef.value = 0;
countRef.value = 0
},
onError: () => {
countRef.value += 1;
countRef.value += 1
if (retryCount === -1 || countRef.value <= retryCount) {
// Exponential backoff
const timeout = retryInterval ?? Math.min(1000 * 2 ** countRef.value, 30000);
const timeout = retryInterval ?? Math.min(1000 * 2 ** countRef.value, 30000)
timerRef.value = setTimeout(() => {
triggerByRetry.value = true;
fetchInstance.refresh();
}, timeout);
triggerByRetry.value = true
fetchInstance.refresh()
}, timeout)
} else {
countRef.value = 0;
countRef.value = 0
}
},
onCancel: () => {
countRef.value = 0;
countRef.value = 0
if (timerRef.value) {
clearTimeout(timerRef.value);
clearTimeout(timerRef.value)
}
},
};
};
}
}
export default useRetryPlugin;
export default useRetryPlugin

View File

@@ -1,34 +1,34 @@
import type { DebouncedFunc, ThrottleSettings } from 'lodash-es';
import { throttle } from 'lodash-es';
import { ref, watchEffect } from 'vue';
import type { DebouncedFunc, ThrottleSettings } from 'lodash-es'
import { throttle } from 'lodash-es'
import { ref, watchEffect } from 'vue'
import type { UseRequestPlugin } from '../types';
import type { UseRequestPlugin } from '../types'
const useThrottlePlugin: UseRequestPlugin<any, any[]> = (
fetchInstance,
{ throttleWait, throttleLeading, throttleTrailing },
) => {
const throttledRef = ref<DebouncedFunc<any>>();
const throttledRef = ref<DebouncedFunc<any>>()
const options: ThrottleSettings = {};
const options: ThrottleSettings = {}
if (throttleLeading !== undefined) {
options.leading = throttleLeading;
options.leading = throttleLeading
}
if (throttleTrailing !== undefined) {
options.trailing = throttleTrailing;
options.trailing = throttleTrailing
}
watchEffect(() => {
if (throttleWait) {
const _originRunAsync = fetchInstance.runAsync.bind(fetchInstance);
const _originRunAsync = fetchInstance.runAsync.bind(fetchInstance)
throttledRef.value = throttle(
(callback) => {
callback();
callback()
},
throttleWait,
options,
);
)
// throttle runAsync should be promise
// https://github.com/lodash/lodash/issues/4400#issuecomment-834800398
@@ -37,27 +37,27 @@ const useThrottlePlugin: UseRequestPlugin<any, any[]> = (
throttledRef.value?.(() => {
_originRunAsync(...args)
.then(resolve)
.catch(reject);
});
});
};
.catch(reject)
})
})
}
return () => {
fetchInstance.runAsync = _originRunAsync;
throttledRef.value?.cancel();
};
fetchInstance.runAsync = _originRunAsync
throttledRef.value?.cancel()
}
}
});
})
if (!throttleWait) {
return {};
return {}
}
return {
onCancel: () => {
throttledRef.value?.cancel();
throttledRef.value?.cancel()
},
};
};
}
}
export default useThrottlePlugin;
export default useThrottlePlugin

View File

@@ -1,93 +1,93 @@
import type { MaybeRef, Ref, WatchSource } from 'vue';
import type { MaybeRef, Ref, WatchSource } from 'vue'
import type Fetch from './Fetch';
import type { CachedData } from './utils/cache';
import type Fetch from './Fetch'
import type { CachedData } from './utils/cache'
export type Service<TData, TParams extends any[]> = (...args: TParams) => Promise<TData>;
export type Subscribe = () => void;
export type Service<TData, TParams extends any[]> = (...args: TParams) => Promise<TData>
export type Subscribe = () => void
// for Fetch
export interface FetchState<TData, TParams extends any[]> {
loading: boolean;
params?: TParams;
data?: TData;
error?: Error;
loading: boolean
params?: TParams
data?: TData
error?: Error
}
export interface PluginReturn<TData, TParams extends any[]> {
onBefore?: (params: TParams) =>
| ({
stopNow?: boolean;
returnNow?: boolean;
stopNow?: boolean
returnNow?: boolean
} & Partial<FetchState<TData, TParams>>)
| void;
| void
onRequest?: (
service: Service<TData, TParams>,
params: TParams,
) => {
servicePromise?: Promise<TData>;
};
servicePromise?: Promise<TData>
}
onSuccess?: (data: TData, params: TParams) => void;
onError?: (e: Error, params: TParams) => void;
onFinally?: (params: TParams, data?: TData, e?: Error) => void;
onCancel?: () => void;
onMutate?: (data: TData) => void;
onSuccess?: (data: TData, params: TParams) => void
onError?: (e: Error, params: TParams) => void
onFinally?: (params: TParams, data?: TData, e?: Error) => void
onCancel?: () => void
onMutate?: (data: TData) => void
}
// for useRequestImplement
export interface UseRequestOptions<TData, TParams extends any[]> {
manual?: MaybeRef<boolean>;
manual?: MaybeRef<boolean>
onBefore?: (params: TParams) => void;
onSuccess?: (data: TData, params: TParams) => void;
onError?: (e: Error, params: TParams) => void;
onBefore?: (params: TParams) => void
onSuccess?: (data: TData, params: TParams) => void
onError?: (e: Error, params: TParams) => void
// formatResult?: (res: any) => TData;
onFinally?: (params: TParams, data?: TData, e?: Error) => void;
onFinally?: (params: TParams, data?: TData, e?: Error) => void
defaultParams?: TParams;
defaultParams?: TParams
// refreshDeps
refreshDeps?: WatchSource<any>[];
refreshDepsAction?: () => void;
refreshDeps?: WatchSource<any>[]
refreshDepsAction?: () => void
// loading delay
loadingDelay?: number;
loadingDelay?: number
// polling
pollingInterval?: number;
pollingWhenHidden?: boolean;
pollingErrorRetryCount?: number;
pollingInterval?: number
pollingWhenHidden?: boolean
pollingErrorRetryCount?: number
// refresh on window focus
refreshOnWindowFocus?: boolean;
focusTimespan?: number;
refreshOnWindowFocus?: boolean
focusTimespan?: number
// debounce
debounceWait?: number;
debounceLeading?: boolean;
debounceTrailing?: boolean;
debounceMaxWait?: number;
debounceWait?: number
debounceLeading?: boolean
debounceTrailing?: boolean
debounceMaxWait?: number
// throttle
throttleWait?: number;
throttleLeading?: boolean;
throttleTrailing?: boolean;
throttleWait?: number
throttleLeading?: boolean
throttleTrailing?: boolean
// cache
cacheKey?: string;
cacheTime?: number;
staleTime?: number;
setCache?: (data: CachedData<TData, TParams>) => void;
getCache?: (params: TParams) => CachedData<TData, TParams> | undefined;
cacheKey?: string
cacheTime?: number
staleTime?: number
setCache?: (data: CachedData<TData, TParams>) => void
getCache?: (params: TParams) => CachedData<TData, TParams> | undefined
// retry
retryCount?: number;
retryInterval?: number;
retryCount?: number
retryInterval?: number
// ready
ready?: MaybeRef<boolean>;
ready?: MaybeRef<boolean>
// [key: string]: any;
}
@@ -97,8 +97,8 @@ export interface UseRequestPlugin<TData, TParams extends any[]> {
(
fetchInstance: Fetch<TData, TParams>,
options: UseRequestOptions<TData, TParams>,
): PluginReturn<TData, TParams>;
onInit?: (options: UseRequestOptions<TData, TParams>) => Partial<FetchState<TData, TParams>>;
): PluginReturn<TData, TParams>
onInit?: (options: UseRequestOptions<TData, TParams>) => Partial<FetchState<TData, TParams>>
}
// for index
@@ -109,16 +109,16 @@ export interface UseRequestPlugin<TData, TParams extends any[]> {
// };
export interface UseRequestResult<TData, TParams extends any[]> {
loading: Ref<boolean>;
data: Ref<TData>;
error: Ref<Error>;
params: Ref<TParams | []>;
cancel: Fetch<TData, TParams>['cancel'];
refresh: Fetch<TData, TParams>['refresh'];
refreshAsync: Fetch<TData, TParams>['refreshAsync'];
run: Fetch<TData, TParams>['run'];
runAsync: Fetch<TData, TParams>['runAsync'];
mutate: Fetch<TData, TParams>['mutate'];
loading: Ref<boolean>
data: Ref<TData>
error: Ref<Error>
params: Ref<TParams | []>
cancel: Fetch<TData, TParams>['cancel']
refresh: Fetch<TData, TParams>['refresh']
refreshAsync: Fetch<TData, TParams>['refreshAsync']
run: Fetch<TData, TParams>['run']
runAsync: Fetch<TData, TParams>['runAsync']
mutate: Fetch<TData, TParams>['mutate']
}
export type UseRequestTimeout = ReturnType<typeof setTimeout>;
export type UseRequestTimeout = ReturnType<typeof setTimeout>

View File

@@ -1,41 +1,41 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { onMounted, onUnmounted, toRefs } from 'vue';
import { onMounted, onUnmounted, toRefs } from 'vue'
import Fetch from './Fetch';
import type { Service, UseRequestOptions, UseRequestPlugin, UseRequestResult } from './types';
import Fetch from './Fetch'
import type { Service, UseRequestOptions, UseRequestPlugin, UseRequestResult } from './types'
export function useRequestImplement<TData, TParams extends any[]>(
service: Service<TData, TParams>,
options: UseRequestOptions<TData, TParams> = {},
plugins: UseRequestPlugin<TData, TParams>[] = [],
) {
const { manual = false, ...rest } = options;
const fetchOptions = { manual, ...rest };
const { manual = false, ...rest } = options
const fetchOptions = { manual, ...rest }
const initState = plugins.map((p) => p?.onInit?.(fetchOptions)).filter(Boolean);
const initState = plugins.map((p) => p?.onInit?.(fetchOptions)).filter(Boolean)
const fetchInstance = new Fetch<TData, TParams>(
service,
fetchOptions,
() => {},
Object.assign({}, ...initState),
);
)
fetchInstance.options = fetchOptions;
fetchInstance.options = fetchOptions
// run all plugins hooks
fetchInstance.pluginImpls = plugins.map((p) => p(fetchInstance, fetchOptions));
fetchInstance.pluginImpls = plugins.map((p) => p(fetchInstance, fetchOptions))
onMounted(() => {
if (!manual) {
const params = fetchInstance.state.params || options.defaultParams || [];
const params = fetchInstance.state.params || options.defaultParams || []
// @ts-ignore
fetchInstance.run(...params);
fetchInstance.run(...params)
}
});
})
onUnmounted(() => {
fetchInstance.cancel();
});
fetchInstance.cancel()
})
return {
...toRefs(fetchInstance.state),
@@ -45,5 +45,5 @@ export function useRequestImplement<TData, TParams extends any[]>(
refreshAsync: fetchInstance.refreshAsync.bind(fetchInstance),
run: fetchInstance.run.bind(fetchInstance),
runAsync: fetchInstance.runAsync.bind(fetchInstance),
} as UseRequestResult<TData, TParams>;
} as UseRequestResult<TData, TParams>
}

View File

@@ -1,48 +1,48 @@
type Timer = ReturnType<typeof setTimeout>;
type CachedKey = string | number;
type Timer = ReturnType<typeof setTimeout>
type CachedKey = string | number
export interface CachedData<TData = any, TParams = any> {
data: TData;
params: TParams;
time: number;
data: TData
params: TParams
time: number
}
interface RecordData extends CachedData {
timer: Timer | undefined;
timer: Timer | undefined
}
const cache = new Map<CachedKey, RecordData>();
const cache = new Map<CachedKey, RecordData>()
export const setCache = (key: CachedKey, cacheTime: number, cachedData: CachedData) => {
const currentCache = cache.get(key);
const currentCache = cache.get(key)
if (currentCache?.timer) {
clearTimeout(currentCache.timer);
clearTimeout(currentCache.timer)
}
let timer: Timer | undefined = undefined;
let timer: Timer | undefined = undefined
if (cacheTime > -1) {
// if cache out, clear it
timer = setTimeout(() => {
cache.delete(key);
}, cacheTime);
cache.delete(key)
}, cacheTime)
}
cache.set(key, {
...cachedData,
timer,
});
};
})
}
export const getCache = (key: CachedKey) => {
return cache.get(key);
};
return cache.get(key)
}
export const clearCache = (key?: string | string[]) => {
if (key) {
const cacheKeys = Array.isArray(key) ? key : [key];
cacheKeys.forEach((cacheKey) => cache.delete(cacheKey));
const cacheKeys = Array.isArray(key) ? key : [key]
cacheKeys.forEach((cacheKey) => cache.delete(cacheKey))
} else {
cache.clear();
cache.clear()
}
};
}

View File

@@ -1,23 +1,23 @@
type CachedKey = string | number;
type CachedKey = string | number
const cachePromise = new Map<CachedKey, Promise<any>>();
const cachePromise = new Map<CachedKey, Promise<any>>()
export const getCachePromise = (cacheKey: CachedKey) => {
return cachePromise.get(cacheKey);
};
return cachePromise.get(cacheKey)
}
export const setCachePromise = (cacheKey: CachedKey, promise: Promise<any>) => {
// Should cache the same promise, cannot be promise.finally
// Because the promise.finally will change the reference of the promise
cachePromise.set(cacheKey, promise);
cachePromise.set(cacheKey, promise)
// no use promise.finally for compatibility
promise
.then((res) => {
cachePromise.delete(cacheKey);
return res;
cachePromise.delete(cacheKey)
return res
})
.catch(() => {
cachePromise.delete(cacheKey);
});
};
cachePromise.delete(cacheKey)
})
}

View File

@@ -1,22 +1,22 @@
type Listener = (data: any) => void;
type Listener = (data: any) => void
const listeners: Record<string, Listener[]> = {};
const listeners: Record<string, Listener[]> = {}
export const trigger = (key: string, data: any) => {
if (listeners[key]) {
listeners[key].forEach((item) => item(data));
listeners[key].forEach((item) => item(data))
}
};
}
export const subscribe = (key: string, listener: Listener) => {
if (!listeners[key]) {
listeners[key] = [];
listeners[key] = []
}
listeners[key].push(listener);
listeners[key].push(listener)
return function unsubscribe() {
const index = listeners[key].indexOf(listener);
listeners[key].splice(index, 1);
};
};
const index = listeners[key].indexOf(listener)
listeners[key].splice(index, 1)
}
}

View File

@@ -2,4 +2,4 @@ export const isBrowser = !!(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
);
)

View File

@@ -1,8 +1,8 @@
import { isBrowser } from './isBrowser';
import { isBrowser } from './isBrowser'
export function isDocumentVisible(): boolean {
if (isBrowser) {
return document.visibilityState !== 'hidden';
return document.visibilityState !== 'hidden'
}
return true;
return true
}

View File

@@ -1,2 +1,2 @@
export const isFunction = (value: unknown): value is (...args: any) => any =>
typeof value === 'function';
typeof value === 'function'

View File

@@ -1,8 +1,8 @@
import { isBrowser } from './isBrowser';
import { isBrowser } from './isBrowser'
export function isOnline(): boolean {
if (isBrowser && typeof navigator.onLine !== 'undefined') {
return navigator.onLine;
return navigator.onLine
}
return true;
return true
}

View File

@@ -1,12 +1,12 @@
export function limit(fn: any, timespan: number) {
let pending = false;
let pending = false
return (...args: any[]) => {
if (pending) return;
pending = true;
fn(...args);
if (pending) return
pending = true
fn(...args)
setTimeout(() => {
pending = false;
}, timespan);
};
pending = false
}, timespan)
}
}

View File

@@ -1,30 +1,30 @@
import { isBrowser } from './isBrowser';
import { isDocumentVisible } from './isDocumentVisible';
import { isOnline } from './isOnline';
import { isBrowser } from './isBrowser'
import { isDocumentVisible } from './isDocumentVisible'
import { isOnline } from './isOnline'
type Listener = () => void;
type Listener = () => void
const listeners: Listener[] = [];
const listeners: Listener[] = []
if (isBrowser) {
const revalidate = () => {
if (!isDocumentVisible() || !isOnline()) return;
if (!isDocumentVisible() || !isOnline()) return
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
const listener = listeners[i]
listener()
}
};
window.addEventListener('visibilitychange', revalidate, false);
window.addEventListener('focus', revalidate, false);
}
window.addEventListener('visibilitychange', revalidate, false)
window.addEventListener('focus', revalidate, false)
}
export default function subscribe(listener: Listener) {
listeners.push(listener);
listeners.push(listener)
return function unsubscribe() {
const index = listeners.indexOf(listener);
const index = listeners.indexOf(listener)
if (index > -1) {
listeners.splice(index, 1);
listeners.splice(index, 1)
}
};
}
}

View File

@@ -1,25 +1,25 @@
import { isBrowser } from './isBrowser';
import { isDocumentVisible } from './isDocumentVisible';
import { isBrowser } from './isBrowser'
import { isDocumentVisible } from './isDocumentVisible'
type Listener = () => void;
type Listener = () => void
const listeners: Listener[] = [];
const listeners: Listener[] = []
if (isBrowser) {
const revalidate = () => {
if (!isDocumentVisible()) return;
if (!isDocumentVisible()) return
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
const listener = listeners[i]
listener()
}
};
window.addEventListener('visibilitychange', revalidate, false);
}
window.addEventListener('visibilitychange', revalidate, false)
}
export default function subscribe(listener: Listener) {
listeners.push(listener);
listeners.push(listener)
return function unsubscribe() {
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
const index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}

View File

@@ -1,60 +1,60 @@
import { shallowRef, unref } from 'vue';
import { shallowRef, unref } from 'vue'
interface UseScrollToOptions {
el: any;
to: number;
duration?: number;
callback?: () => any;
el: any
to: number
duration?: number
callback?: () => any
}
function easeInOutQuad(t: number, b: number, c: number, d: number) {
t /= d / 2;
t /= d / 2
if (t < 1) {
return (c / 2) * t * t + b;
return (c / 2) * t * t + b
}
t--;
return (-c / 2) * (t * (t - 2) - 1) + b;
t--
return (-c / 2) * (t * (t - 2) - 1) + b
}
function move(el: HTMLElement, amount: number) {
el.scrollTop = amount;
el.scrollTop = amount
}
const position = (el: HTMLElement) => {
return el.scrollTop;
};
return el.scrollTop
}
function useScrollTo({ el, to, duration = 500, callback }: UseScrollToOptions) {
const isActiveRef = shallowRef(false);
const start = position(el);
const change = to - start;
const increment = 20;
let currentTime = 0;
const isActiveRef = shallowRef(false)
const start = position(el)
const change = to - start
const increment = 20
let currentTime = 0
const animateScroll = function () {
if (!unref(isActiveRef)) {
return;
return
}
currentTime += increment;
const val = easeInOutQuad(currentTime, start, change, duration);
move(el, val);
currentTime += increment
const val = easeInOutQuad(currentTime, start, change, duration)
move(el, val)
if (currentTime < duration && unref(isActiveRef)) {
requestAnimationFrame(animateScroll);
requestAnimationFrame(animateScroll)
} else {
if (callback && typeof callback === 'function') {
callback();
callback()
}
}
};
}
const run = () => {
isActiveRef.value = true;
animateScroll();
};
isActiveRef.value = true
animateScroll()
}
const stop = () => {
isActiveRef.value = false;
};
isActiveRef.value = false
}
return { start: run, stop };
return { start: run, stop }
}
export { useScrollTo, type UseScrollToOptions };
export { useScrollTo, type UseScrollToOptions }

View File

@@ -1,40 +1,40 @@
import { type AnyFunction } from '@vben/types';
import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core';
import { type AnyFunction } from '@vben/types'
import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core'
interface UseWindowSizeOptions {
wait?: number;
once?: boolean;
immediate?: boolean;
listenerOptions?: AddEventListenerOptions | boolean;
wait?: number
once?: boolean
immediate?: boolean
listenerOptions?: AddEventListenerOptions | boolean
}
function useWindowSizeFn(fn: AnyFunction, options: UseWindowSizeOptions = {}) {
const { wait = 150, immediate } = options;
const { wait = 150, immediate } = options
let handler = () => {
fn();
};
const handleSize = useDebounceFn(handler, wait);
handler = handleSize;
fn()
}
const handleSize = useDebounceFn(handler, wait)
handler = handleSize
const start = () => {
if (immediate) {
handler();
handler()
}
window.addEventListener('resize', handler);
};
window.addEventListener('resize', handler)
}
const stop = () => {
window.removeEventListener('resize', handler);
};
window.removeEventListener('resize', handler)
}
tryOnMounted(() => {
start();
});
start()
})
tryOnUnmounted(() => {
stop();
});
return { start, stop };
stop()
})
return { start, stop }
}
export { useWindowSizeFn, type UseWindowSizeOptions };
export { useWindowSizeFn, type UseWindowSizeOptions }

View File

@@ -1,4 +1,4 @@
import { defineBuildConfig } from 'unbuild';
import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
clean: true,
@@ -7,4 +7,4 @@ export default defineBuildConfig({
rollup: {
emitCJS: true,
},
});
})

View File

@@ -1 +1 @@
export * from './utils';
export * from './utils'

View File

@@ -1,49 +1,49 @@
/**
* 任意类型的异步函数
*/
type AnyPromiseFunction = (...arg: any[]) => PromiseLike<any>;
type AnyPromiseFunction = (...arg: any[]) => PromiseLike<any>
/**
* 任意类型的普通函数
*/
type AnyNormalFunction = (...arg: any[]) => any;
type AnyNormalFunction = (...arg: any[]) => any
/**
* 任意类型的函数
*/
type AnyFunction = AnyNormalFunction | AnyPromiseFunction;
type AnyFunction = AnyNormalFunction | AnyPromiseFunction
/**
* T | null 包装
*/
type Nullable<T> = T | null;
type Nullable<T> = T | null
/**
* T | Not null 包装
*/
type NonNullable<T> = T extends null | undefined ? never : T;
type NonNullable<T> = T extends null | undefined ? never : T
/**
* 字符串类型对象
*/
type Recordable<T = any> = Record<string, T>;
type Recordable<T = any> = Record<string, T>
/**
* 字符串类型对象(只读)
*/
interface ReadonlyRecordable<T = any> {
readonly [key: string]: T;
readonly [key: string]: T
}
/**
* setTimeout 返回值类型
*/
type TimeoutHandle = ReturnType<typeof setTimeout>;
type TimeoutHandle = ReturnType<typeof setTimeout>
/**
* setInterval 返回值类型
*/
type IntervalHandle = ReturnType<typeof setInterval>;
type IntervalHandle = ReturnType<typeof setInterval>
export {
type AnyFunction,
@@ -55,4 +55,4 @@ export {
type ReadonlyRecordable,
type Recordable,
type TimeoutHandle,
};
}