usePersistFn
usePersistFn可以持久化function,保证函数地址永远不会变化。
import { useRef } from 'react';
export type noop = (...args: any[]) => any;
function usePersistFn<T extends noop>(fn: T) {
const fnRef = useRef<T>(fn);
// 每次渲染fn的最新值都会记录在fnRef中
fnRef.current = fn;
const persistFn = useRef<T>();
// 初次渲染时给persistFn赋值,此后persistFn不会更新
if (!persistFn.current) {
persistFn.current = function (...args) {
return fnRef.current!.apply(this, args);
} as T;
}
// 返回persistFn,感叹号表示返回值类型非null或undefined,因为初次渲染时persistFn就被赋值为了函数
return persistFn.current!;
}
export default usePersistFn;
为什么要用usePersistFn?
在React官方文档中提到
在某些场景中,你可能会需要用 useCallback 记住一个回调,但由于内部函数必须经常重新创建,记忆效果不是很好,导致子组件重复 render。对于超级复杂的子组件,重新渲染会对性能造成影响。通过 usePersistFn,可以保证函数地址永远不会变化。
官方给出的demo如下
function Form() {
const [text, updateText] = useState('');
const textRef = useRef();
useEffect(() => {
textRef.current = text; // 把它写入 ref
});
const handleSubmit = useCallback(() => {
const currentText = textRef.current; // 从 ref 读取它
alert(currentText);
}, [textRef]); // 不要像 [text] 那样重新创建 handleSubmit
return (
<>
<input value={text} onChange={e => updateText(e.target.value)} />
<ExpensiveTree onSubmit={handleSubmit} />
</>
);
}
复制代码
ExpensiveTree是一个复杂的子组件,其接受一个props handleSubmit函数。如果使用useCallback,由于handleSubmit函数内部使用了text变量,便要写为如下形式:
const handleSubmit = useCallback(() => {
alert(text);
}, [text]);
复制代码
只要text发生变化,useCallback接收的内部函数便要重新创建,导致handleSubmit函数的引用地址发生变化。进而引起子组件ExpensiveTree的重渲染,对性能产生影响。
usePersistFn的目标便是持久化接收的函数,且调用时内部函数引用的变量(上例为text)能获取到实时的值(useCallback的依赖传空数组也能实现持久化函数,但无法获取实时的值)
官方给的demo中更新textRef写在了useEffect中,为什么usePersistFn不这样实现?如果在子组件的useEffect回调函数中调用usePersistFn就会出现问题。因为渲染时会先执行子组件的useEffect,后执行父组件自定义hooks的useEffect。
文章出自:前端餐厅ReTech,如有转载本文请联系前端餐厅ReTech今日头条号。
github:https://github.com/zuopf769