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??