前言
作为一名前端开发者,你是否遇到过React
组件渲染时画面闪烁的问题?明明代码逻辑没问题,却出现了短暂的视觉跳动?这很可能是因为你使用了useEffect
而不是useLayoutEffect
。今天,我将带你详细了解这两个Hook的区别,以及如何正确使用useLayoutEffect
来提升用户体验。
useEffect vs useLayoutEffect
useEffect
useEffect
是React中处理副作用的标准方式,其执行时机为:
- 在渲染完成后异步执行
- 组件更新时执行
- 组件卸载前执行清理函数
jsx
useEffect(() => {
// 副作用代码
return () => {
// 清理函数
}
}, [依赖项]);
useLayoutEffect
而useLayoutEffect
与useEffect
有着关键区别:
- 在DOM更新之后同步触发
- 会阻塞页面的渲染
- 在浏览器绘制之前执行
jsx
useLayoutEffect(() => {
// DOM操作代码
return () => {
// 清理函数
}
}, [依赖项]);
执行时机对比
两者的执行顺序如下:
- React更新DOM
useLayoutEffect
执行- 浏览器绘制屏幕
useEffect
执行
为了更好地理解两者区别,我们来看一下它们的执行顺序:
text
【React更新DOM】→【useLayoutEffect执行】→【浏览器绘制】→【useEffect执行】
这种执行顺序的差异导致了它们适用于不同的场景。
js
function App() {
// 响应式对象
const boxRef = useRef();
console.log(boxRef.current,boxRef);
useEffect(()=>{
console.log('useEffect height',boxRef.current.offsetHeight);
},[])
useLayoutEffect(()=>{
console.log('useLayoutEffect height',boxRef.current.offsetHeight);
},[])
可以看到LayoutEffect是在useEffect之前行。
useLayoutEffect解决的问题
防止闪烁
最常见的使用场景是解决组件"闪烁"问题。当你需要在DOM更新后立即进行样式计算或DOM操作,并且不希望用户看到中间状态时,useLayoutEffect
是最佳选择。
同步获取DOM元素尺寸
当你需要在渲染后立即获取DOM元素的尺寸并基于此进行其他计算时,useLayoutEffect
能确保你获取的是最新的DOM信息。
实际案例
让我们看一个简单例子,展示两者的区别:
jsx
// 使用useEffect的版本
function FlickeringComponent() {
const [width, setWidth] = useState(0);
useEffect(() => {
// 会导致闪烁,因为先渲染了width=0的状态
setWidth(document.getElementById('myElement').getBoundingClientRect().width);
}, []);
return (
<div id="myElement" style={{ width: width ? `${width}px` : '100%' }}>
内容区域
</div>
);
}
// 使用useLayoutEffect的版本
function SmoothComponent() {
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
// 不会闪烁,因为在浏览器绘制前就更新了状态
setWidth(document.getElementById('myElement').getBoundingClientRect().width);
}, []);
return (
<div id="myElement" style={{ width: width ? `${width}px` : '100%' }}>
内容区域
</div>
);
}
再来看以下代码来展示两个React hooks的执行时机区别:
js
import {
useState,
useEffect,
useLayoutEffect,
useRef
} from 'react'
import './App.css'
import { use } from 'react'
function App() {
// 响应式对象
const boxRef = useRef();
console.log(boxRef.current, boxRef);
useEffect(() => {
console.log('useEffect height', boxRef.current.offsetHeight);
}, [])
useLayoutEffect(() => {
console.log('useLayoutEffect height', boxRef.current.offsetHeight);
}, [])
return (
<>
<div ref={boxRef} style={{ height: 100 }}></div>
</>
)
}
export default App
让我们看到打印的效果:可以看到useLayoutEffect会在浏览器绘制前同步执行,useEffect在浏览器绘制后异步执行,不阻塞渲染。

现在到了大家最期待的节目了,解决页面闪烁的问题,现在大家跟着我们一起来看到下面的代码。
js
function Modal() {
const ref = useRef();
useEffect(() => {
const height = ref.current.offsetHeight
const width = ref.current.offsetWidth
ref.current.style.marginTop = `${(window.innerHeight - height) / 2}px`
ref.current.style.marginLeft = `${(window.innerWidth - width) / 2}px`
}, [])
return <div ref={ref} style={{ background: 'lightblue', position: 'absolute', height: '200px', width: '200px' }}>我是弹窗</div>
}
function App() {
return (
<>
<Modal />
</>
)
}
export default App
可以看到使用useEffect页面的刷新效果:

js
function Modal() {
const ref = useRef();
useLayoutEffect(() => {
const height = ref.current.offsetHeight
const width = ref.current.offsetWidth
ref.current.style.marginTop = `${(window.innerHeight - height) / 2}px`
ref.current.style.marginLeft = `${(window.innerWidth - width) / 2}px`
}, [])
return <div ref={ref} style={{ background: 'lightblue', position: 'absolute', height: '200px', width: '200px' }}>我是弹窗</div>
}
function App() {
return (
<>
<Modal />
</>
)
}
export default App
现在让我们继续往下看使用了useLayoutEffect的页面刷新效果:

可以看到使用该hook成功解决了页面闪烁的问题,这样我们就大功告成了,使用户交互体验效果更好。
性能考虑
虽然useLayoutEffect
能解决特定问题,但它是同步执行的,会阻塞浏览器绘制。如果在其中执行耗时操作,可能导致页面响应变慢。因此:
- 仅在必要时使用
useLayoutEffect
- 保持内部逻辑简洁高效
- 对于不涉及DOM测量或不会引起视觉闪烁的副作用,优先使用
useEffect
最佳实践
- 默认使用useEffect :大多数情况下,异步的
useEffect
就足够了 - 视觉问题时考虑useLayoutEffect:出现闪烁或需要同步DOM测量时使用
- 避免复杂计算 :在
useLayoutEffect
中避免进行耗时计算 - 谨慎使用状态更新 :在
useLayoutEffect
中更新状态可能导致额外渲染
📊 useEffect vs useLayoutEffect 总结表
特性 | useEffect | useLayoutEffect |
---|---|---|
执行时机 | 渲染后异步执行 | DOM更新后同步执行 |
是否阻塞渲染 | 否 | 是 |
适用场景 | 数据获取、订阅、定时器 | DOM测量、防闪烁、视觉更新 |
性能影响 | 较小 | 可能导致页面卡顿 |
SSR兼容性 | 良好 | 会有警告 |
总结
useLayoutEffect
是解决特定UI问题的有力工具,尤其是在处理需要同步DOM操作的场景。理解useEffect
和useLayoutEffect
的区别,能帮助你在合适的场景选择合适的工具,构建更流畅的React应用。
记住,虽然useLayoutEffect
能解决闪烁问题,但它也可能带来性能问题。在开发中要权衡利弊,合理使用。
你是否在项目中遇到过需要使用useLayoutEffect
的场景?欢迎在评论区分享你的经验!