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

useLatest是否属于渲染期间修改? #2495

Closed
coding-ice opened this issue Mar 7, 2024 · 5 comments
Closed

useLatest是否属于渲染期间修改? #2495

coding-ice opened this issue Mar 7, 2024 · 5 comments

Comments

@coding-ice
Copy link
Contributor

@liuyib 大佬请问一个问题

function useLatest(value) {
  const ref = useRef(value);
  ref.current = value;

  return ref;
}

https://react.docschina.org/reference/react/useRef#avoiding-recreating-the-ref-contents
通常情况下,在渲染过程中写入或读取 ref.current 是不允许的

允许做法

if (signal) {
  ref.current = value
}
@crazylxr
Copy link
Collaborator

crazylxr commented Mar 8, 2024

合理,理论上可以增加一个变化检测再给 ref.current 赋值。

function useLatest(value) {
  const ref = useRef(null);
  if(value !== ref.curent) {
       ref.current = value;
  }
  return ref;
}

这样可能更好一点。避免了每次渲染都赋值。

@crazylxr crazylxr added the v4 label Mar 8, 2024
@coding-ice
Copy link
Contributor Author

合理,理论上可以增加一个变化检测再给 ref.current 赋值。

function useLatest(value) {
  const ref = useRef(null);
  if(value !== ref.curent) {
       ref.current = value;
  }
  return ref;
}

这样可能更好一点。避免了每次渲染都赋值。

交给我吧,感谢~

@liuyib
Copy link
Collaborator

liuyib commented Mar 11, 2024

关于 React 官方文档里声明的 Do not write or read ref.current during rendering. 这个我确实思考了很久很久,说下个人的愚见吧。

原本一直以为一刀切的方式,绝对遵循这个原则就好了,但是业务逻辑中有很多不好绕过的闭包问题,需要 ref 穿透一下,这样做并发模式下可能存在问题,但也立竿见影地把闭包问题解决了,所以有时我们还离不开渲染阶段读写 ref 的操作。

为了拥抱 React 未来,拥抱并发模式,我们要遵循这个原则,但也不是要一刀切的方式去遵循,比如,官方自己也举例了初始赋值时可以在渲染阶段读写 refhttps://react.dev/reference/react/useRef#avoiding-recreating-the-ref-contents, 说明只要行为可控,是可以打破上述原则的。

首先,渲染阶段读写 ref 并直接参与 DOM 渲染是不允许的,因为函数不再是纯的:
image

其次,在“副作用”(side effects,useEffect、事件处理程序、定时器等)中读写 ref 是允许的:
image

所以我认为,在渲染阶段“写 ref”来保存一个变量的最新引用,并在“副作用”中“读 ref”来穿透闭包,并没有导致不可控,也不会导致函数不纯,只要危险操作被收敛到了“副作用”区域,就是合法的


回到 useLatest 这个 hook,单从逻辑上看,它违背了最上面的原则。但是别忘了这个 hook 的能力定位,它是用来穿透闭包的(或者说绕过闭包问题),所以如果不乱用这个 hook,那么恰好符合了我上面认为的观点(useLatest 渲染阶段“写 ref”,我们用它穿透闭包“读 ref”)。举个反面例子:

const [count] = useState(0);
// 渲染阶段“写 ref”了(useLatest 内部会写 ref)
const countRef = useLatest(count);

// 然后,又在渲染阶段“读 ref” 了
return <div>{countRef.current}</div>;

这个例子在渲染阶段既写 ref,又读 ref,危险操作被收敛到 DOM 上,导致函数不纯,这样肯定是不行的。

总之,useLatest 的设计初衷是为了解决闭包问题,并不希望直接参与 DOM 渲染,因此只要不乱用,并不会破坏读写 ref 的规则。


转 ahooks 作者大龙哥的建议:useLatest 存函数输入,useMemoizedFn 存函数输出。这样用没什么问题~

如果真有问题的话,那就水多了加面,面多了加水,没法一棒子打死。

@liuyib
Copy link
Collaborator

liuyib commented Mar 11, 2024

个人感觉目前 useLatest 的源码不太需要修改,主要是文档需要说明:useLatest 的返回值应该只用于在“副作用”区域解决闭包问题,不能直接用于 DOM 渲染

@crazylxr
Copy link
Collaborator

额嗯 ,问题不大。这个可以先不改

@crazylxr crazylxr removed the v4 label Mar 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants