Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ahooks源码分析之usePersistFn #11

Open
susucain opened this issue Apr 12, 2021 · 2 comments
Open

ahooks源码分析之usePersistFn #11

susucain opened this issue Apr 12, 2021 · 2 comments
Labels

Comments

@susucain
Copy link
Owner

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不这样实现?

参见这个issue

如果在子组件的useEffect回调函数中调用usePersistFn就会出现问题。因为渲染时会先执行子组件的useEffect,后执行父组件自定义hooks的useEffect

demo参见在useEffect中更新fnRef

@susucain susucain added the React label Apr 12, 2021
@andyyxw
Copy link

andyyxw commented Aug 7, 2021

请问persistFn定义和赋值为什么要分两行,直接:

const persistFn = useRef<T>(function (...args) {
  return fnRef.current!.apply(this, args);
} as T)

有区别吗?
还有,这里不用箭头函数,也不一样吗?

@susucain
Copy link
Owner Author

请问persistFn定义和赋值为什么要分两行,直接:

const persistFn = useRef<T>(function (...args) {
  return fnRef.current!.apply(this, args);
} as T)

有区别吗? 还有,这里不用箭头函数,也不一样吗?

不好意思现在才看到。定义赋值可以不分开写的,没有区别,这里我是贴的ahooks源码😂。用箭头函数会更改函数调用时的this指向,这里只能用function。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants