useRef 初始化陷阱

37 阅读1分钟
  const resolveRef = useRef<(value: MergeResult) => void>();
  const promiseRef = useRef<Promise<MergeResult>>(new Promise(resolve => {
    resolveRef.current = resolve;
  }));

这段代码有什么问题?

本意是在一个hook中维护同一个promise和其对应的resolve

因为ref在整个组件的生命周期里都维持对同一个对象的引用,所以浅显的以为这段代码会按预期工作

但并不是,因为我忽略了useRef(initialValue)的initialValue会多次运行,useRef 的初始化表达式会在每次渲染时执行(尽管返回值会被丢弃),所以resolveRef就总是指向这个新的resolve,promise和resolve对应不上自然不会按预期工作

那改成下面这种呢?

  const resolveRef = useRef<(value: MergeResult) => void>();
  const promiseRef = useRef<Promise<MergeResult>>(new Promise(resolve => {
    if (!resolveRef.current) {
      resolveRef.current = resolve;
    }
  }));

可以,或者

  const resolveRef = useRef<(value: MergeResult) => void>();
  const promiseRef = useRef<Promise<MergeResult>>();

  useEffect(() => {
    promiseRef.current = new Promise(resolve => {
      resolveRef.current = resolve;
    });
    return () => {
      resolveRef.current = undefined;
      promiseRef.current = undefined;
    };
  }, []);

so,ref 的唯一性怎么保证的?

初始化阶段(mount)

调用 mountRef(initialValue) 时,通过 mountWorkInProgressHook 创建新的 Hook 对象,将其添加到当前 fiber 的 hook 链表中。

初始化 hook.memoizedState 为一个包含 current 属性的对象,初始值为传入的 initialValue

javascriptfunction updateRef<T>(initialValue: T): { current: T } {
  const hook = updateWorkInProgressHook();
  return hook.memoizedState; // 返回之前的 ref 对象
}

2. 更新阶段(update)

在 updateRef 中,通过 updateWorkInProgressHook 复用上一个生命周期的 Hook。

hook.memoizedState 保持不变,即 ref 对象始终指向同一实例,其 current 属性可动态修改而不触发渲染。

javascriptfunction mountRef<T>(initialValue: T): { current: T } {
  const hook = mountWorkInProgressHook();
  const ref = { current: initialValue };
  hook.memoizedState = ref;
  return ref;
}