ant-design-vue/components/_util/cssinjs/hooks/useGlobalCache.tsx

116 lines
3.3 KiB
Vue

import { useStyleInject } from '../StyleContext';
import type { KeyType } from '../Cache';
import useCompatibleInsertionEffect from './useCompatibleInsertionEffect';
import useHMR from './useHMR';
import type { ShallowRef, Ref } from 'vue';
import { onBeforeUnmount, watch, computed } from 'vue';
export type ExtractStyle<CacheValue> = (
cache: CacheValue,
effectStyles: Record<string, boolean>,
options?: {
plain?: boolean;
},
) => [order: number, styleId: string, style: string] | null;
export default function useGlobalCache<CacheType>(
prefix: string,
keyPath: Ref<KeyType[]>,
cacheFn: () => CacheType,
onCacheRemove?: (cache: CacheType, fromHMR: boolean) => void,
// Add additional effect trigger by `useInsertionEffect`
onCacheEffect?: (cachedValue: CacheType) => void,
): ShallowRef<CacheType> {
const styleContext = useStyleInject();
const globalCache = computed(() => styleContext.value?.cache);
const deps = computed(() => [prefix, ...keyPath.value].join('%'));
const HMRUpdate = useHMR();
type UpdaterArgs = [times: number, cache: CacheType];
const buildCache = (updater?: (data: UpdaterArgs) => UpdaterArgs) => {
globalCache.value.update(deps.value, prevCache => {
const [times = 0, cache] = prevCache || [undefined, undefined];
// HMR should always ignore cache since developer may change it
let tmpCache = cache;
if (process.env.NODE_ENV !== 'production' && cache && HMRUpdate) {
onCacheRemove?.(tmpCache, HMRUpdate);
tmpCache = null;
}
const mergedCache = tmpCache || cacheFn();
const data: UpdaterArgs = [times, mergedCache];
// Call updater if need additional logic
return updater ? updater(data) : data;
});
};
watch(
deps,
() => {
buildCache();
},
{ immediate: true },
);
let cacheEntity = globalCache.value.get(deps.value);
// HMR clean the cache but not trigger `useMemo` again
// Let's fallback of this
// ref https://github.com/ant-design/cssinjs/issues/127
if (process.env.NODE_ENV !== 'production' && !cacheEntity) {
buildCache();
cacheEntity = globalCache.value.get(deps.value);
}
const cacheContent = computed(
() =>
(globalCache.value.get(deps.value) && globalCache.value.get(deps.value)![1]) ||
cacheEntity![1],
);
// Remove if no need anymore
useCompatibleInsertionEffect(
() => {
onCacheEffect?.(cacheContent.value);
},
polyfill => {
// It's bad to call build again in effect.
// But we have to do this since StrictMode will call effect twice
// which will clear cache on the first time.
buildCache(([times, cache]) => {
if (polyfill && times === 0) {
onCacheEffect?.(cacheContent.value);
}
return [times + 1, cache];
});
return () => {
globalCache.value.update(deps.value, prevCache => {
const [times = 0, cache] = prevCache || [];
const nextCount = times - 1;
if (nextCount <= 0) {
if (polyfill || !globalCache.value.get(deps.value)) {
onCacheRemove?.(cache, false);
}
return null;
}
return [times - 1, cache];
});
};
},
deps,
);
onBeforeUnmount(() => {
buildCache();
});
return cacheContent;
}