精读React hooks(六):useLayoutEffect解决了什么问题?
如果你会用useEffect
(精读React hook(五):useEffect使用细节知多少?),那么你一定也会用useLayoutEffect
。因为它们的用法一模一样。
useEffect
和useLayoutEffect
的区别仅有一个:
useEffect
执行时机是在React的渲染和提交阶段之后;
useLayoutEffect
执行时机是在React的提交阶段之后,但在浏览器实际绘制屏幕之前。
useEffect vs useLayoutEffect
我们通过一个例子来看看阻塞和非阻塞对用户来说有什么区别。
这个例子写了两个方块,分别使用useEffect
和useLayoutEffect
来更新高度,代码实际效果在我的演示站查看。
当你在性能较差的设备上肉眼可以明显看到区别:
useEffect
的方块会闪一下
useLayoutEffect
的方块则不会闪
如果你的电脑性能比较好,可以尝试多次刷新,也有一定几率看到useEffect
的闪动。
我们应该这样描述二者的区别:
useEffect
: 执行时机是在React的渲染和提交阶段之后。这意味着当任何相关DOM更改被应用并且组件已被重新渲染后,useEffect
里的代码会执行。但它是异步的,所以可能会在浏览器的下一个绘制周期之后才执行。
useLayoutEffect
: 执行时机是在React的提交阶段之后,但在浏览器实际绘制屏幕之前。这使得你可以同步地读取或更改DOM,然后让浏览器在下一次绘制时立即体现这些更改,从而避免不必要的闪烁或布局跳动。
useLayoutEffect的作用
我们已经清楚了useLayoutEffect
的特性了,那么可以猜想,useLayoutEffect
是作用于这样的场景:需要在浏览器绘制前获取 DOM 元素的大小或位置,或者在浏览器绘制前修改 DOM。
这里有一个非常典型的场景——tooltip 组件。我们就来写一个 tooltip 组件,应用useLayoutEffect
来自适应设置 tooltip 位置。
我们的需求是:鼠标移入一个按钮,能够判断 tooltip 展示区域,如果按钮上方空间足够,则显示在上方,如果按钮上方空间不够,则自适应显示在按钮下方。
为了保证没有页面抖动,我们要使用useLayoutEffect
来更新显示的位置,示例代码如下:
这里示例中,我们写了三个按钮,每次鼠标移入按钮的时候,计算按钮到父级上沿的空间是否可以容纳一个 tooltip,如果足够,tooltip 就在按钮上方展示,如果不够,则在按钮下方展示。实际效果如图:
你也可以到演示站试一试。
总结
最后,我们再明确一下useEffect
和useLayoutEffect
分别在何时使用、useLayoutEffect
的使用注意事项。
何时使用useEffect
- 副作用与DOM无关:例如,数据获取、设置订阅、手动更改浏览器的URL等。
- 不需要立即同步读取或更改DOM:如果你不关心可能的微小布局跳动或闪烁,那么
useEffect
就足够了。
- 性能考虑:
useEffect
通常对性能影响较小,因为它不会阻塞浏览器渲染。
何时使用useLayoutEffect
- 需要同步读取或更改DOM:例如,你需要读取元素的大小或位置并在渲染前进行调整。
- 防止闪烁:在某些情况下,异步的
useEffect
可能会导致可见的布局跳动或闪烁。例如,动画的启动或某些可见的快速DOM更改。
- 模拟生命周期方法:如果你正在将旧的类组件迁移到功能组件,并需要模拟
componentDidMount
、componentDidUpdate
和componentWillUnmount
的同步行为。
使用注意事项
- 避免过度使用
useLayoutEffect
,因为它是同步的,可能会影响应用的性能。只有当你确实需要同步的DOM操作时才使用它。
- 如果代码在服务器端渲染(SSR)中出现问题,考虑回退到
useEffect
。useLayoutEffect
在服务器端渲染时不会运行,可能会引发警告或错误。
专栏资源
专栏博客地址:精读React Hooks
专栏演示站:React Hooks Demos
专栏源码仓库:👉Github - Source Code
交个朋友:👉加入「独立全栈交流群」
专栏文章列表:
精读React hooks(一):useState 的几个基础用法和进阶技巧
精读React hooks(二):React状态管理的强大工具——useReducer
精读React hooks(三):useContext从基础应用到性能优化
精读React hooks(四):useRef的多维用途
精读React hooks(五):useEffect使用细节知多少?
精读React hooks(六):useLayoutEffect解决了什么问题?
精读React hooks(七):用useMemo来减少性能开销
精读React hooks(八):我们为什么需要useCallback
精读React hooks(九):使用useTransition进行非阻塞渲染
精读React hooks(十):使用useDeferredValue延迟状态更新
精读React hooks(十一):useInsertionEffect——CSS-in-JS样式注入新方式
精读React hooks(十二):使用useImperativeHandle能获得什么能力
精读React hooks(十三):使用useSyncExternalStore获取实时数据
精读React hooks(十四):总有一天你会需要useId为你生成唯一id
精读React hooks(十五):把useDebugValue加入你的React调试工具库
精读React hooks(十六):一个为代码优雅而生的hook——use