使用useEffect、useCallback、useMemo等hook时,如果有依赖访问地址不定,依赖数组中添加相应依赖,否则就有闭包陷阱。一两个还好,多了就是一大串,useCallback还不如直接不用,useEffect、useMemo还要计算是哪个数据变化再计算,甚至要做防抖。解决这个问题的一个办法就是让这些函数访问数据的地址恒定,比如放到ref中。组件更新依旧会因为props、states的变化而更新,只需要保证它们变了,ref中的存储关系跟着变,为每个数据建一个地址恒定的getter,hooks被调用时使用getter就能访问到最新的数据。
一个数据:
import { useCallback, useRef } from 'react';
export function useGetProp<T>(prop: T) {
const ref = useRef<{ value: T }>({ value: prop });
ref.current.value = prop;
return useCallback(() => ref.current.value, []);
}
多个数据:
import { useRef, useMemo } from 'react';
import { upperFirst } from 'lodash';
type CreateGetKey<T> = T extends `${infer F}${infer R}`
? `get${Uppercase<F>}${R}`
: never;
type RecoverGetKey<T> = T extends `get${infer F}${infer R}`
? `${Lowercase<F>}${R}`
: never;
type GetKeyMap<T, K extends keyof T = keyof T> = T extends Record<string, any>
? {
[P in CreateGetKey<K>]: () => T[RecoverGetKey<P>];
}
: never;
export function useGetProps<T extends Record<string, any> = Record<string, any>>(
data: T = {} as T,
) {
const ref = useRef<T>(data);
Object.keys(data).forEach((key: Exclude<keyof T, number | symbol>) => {
ref.current[key] = data[key];
});
return useMemo(
() =>
Object.keys(data).reduce(
(pre, key: Exclude<keyof T, number | symbol>) => {
pre[`get${upperFirst(key)}`] = () => ref.current[key];
return pre;
},
{} as GetKeyMap<T>,
),
[],
);
}
// 使用
function Component({prop0, prop1,prop2}){
const getProp = useGetProp(prop0);
const { getProp1, getProp2 } = useGetProp({prop1,prop2});
const onClick= useCallback((e: React.MouseEvent<HTMLElement, MouseEvent>)=>{
const prop1= getProp1();
// ...
},[])
// ...
}
useGetProps能根据传入的变量名计算导出的getter名,而且有类型提示,但是这个类型也是有缺陷的,并不能反映实际生成的全部getter,比如:
const {} = useGetProps({1: prop1})
此时类型认为getter名是数字,不会推导getter名。当然,可以在往ref中存和生成getter时只取符合类型要求的数据。