React useEvent RFC 细节分析:为何 useLayoutEffect 仍然不符合预期

1,489 阅读3分钟

简介

在 2022/5/5 ,react 的新 RFC useEvent 推出,解决的是一个旧问题:zh-hans.reactjs.org/docs/hooks-… 关于具体的问题和使用场景,关键词搜索 useEvent 就有大量的文章,个人这篇 也掰扯过这个问题。

在 useEvent 具体实现中有个细节经常被重复的讨论,即 useLayoutEffect 实际上也不符合预期的问题,这里希望通过最简单的表述来彻底阐明它,解答你可能的疑惑。

image.png

Hook 执行顺序

render 同步,useLayoutEffect 为 Fiber dfs 遍历的同步(时机上高仿可用微任务),useEffect 宏任务。 通过一个简单测试我们明确一下 FC 中 Hooks 执行时机:codesandbox.io/s/test-usee…

父组件 A 内有:

image.png

子组件 B 内有:

image.png

执行结果为:

image.png

则执行顺序是: 父组件render>子组件render> 子组件useLayoutEffect>父组件useLayoutEffect>子组件useEffect>父组件useEffect

useLayoutEffect 执行时机

简单演示:codesandbox.io/s/test-usee…

假如父组件 A 有 logCount 函数,使用 useLayoutEffect 版本的 useEvent 包裹,传给子组件作为 props

image.png

考虑 Hook 执行顺序:父组件render>子组件render> 子组件useLayoutEffect>父组件useLayoutEffect>子组件useEffect>父组件useEffect

则 子组件render阶段 和 子组件useLayoutEffect阶段 因为执行在父组件 useLayoutEffect (给ref赋值的时机) 之前这两个阶段子组件B从props里读取的 logCount 函数不符合预期,是旧的或直接是undefine。这就是 useLayoutEffect 为什么不符合预期的问题。

同理可知官方文档 里使用 useEffect 也不符合预期,和上面一样,子组件在 父组件useEffect 执行前的时机中读取的 logCount 都会是旧的。

但 ref.current 是引用,待父组件的 useLayoutEffect 执行完,子组件内的 logCount 也会变成新的,此时就符合预期了。

直接放 render 里就符合预期,为什么要放 useLayoutEffect

目前绝大部分第三方 Hooks 对这个函数的实现都是赋值直接放 render 里,即不包任何东西:

image.png

考虑 Hook 执行顺序:父组件render>子组件render> 子组件useLayoutEffect>父组件useLayoutEffect>子组件useEffect>父组件useEffect

ref 赋值直接发生在 父组件render 过程中,子组件任何时候读取都符合预期,为什么包在 useLayoutEffect 里?

这个问题主要是为了 concurrent 模式,见React Issue #16956 ,在 concurrent 模式里,在rendering 过程中更新 ref 可能存在问题,Mutating refs during render can cause bugs。详细讨论可以看这里:github.com/facebook/re…

image.png

可能出现的新 Hook 执行时机

最合理的解决方式是官方提供一个【新的hook】,处于一个【新的执行时机】,正面解决【concurrent模式里 rendering 中更新 ref 可能不安全】 的问题,这个新的hook 的执行很明显需要在【组件render 之后】执行,并保持先父组件后子组件的执行顺序。插入原hook 执行顺序则为:

父组件render > 【父组件新hook】 >子组件render > 【子组件新hook】 > 子组件useLayoutEffect>父组件useLayoutEffect>子组件useEffect>父组件useEffect

这就是文章头图问题的解释:

image.png

In a real implementation, this would run before layout effects.

实际上要出的是一个新的 hook 它在 layout effects 之前执行,并保持先父后子的执行顺序。

结语

如果本文对你有一点帮助,求点赞收藏