useEffect vs useLayoutEffect
执行时机「关键区别」
| 钩子 | 触发时机 |
|---|---|
useEffect | 异步执行:在浏览器完成绘制paint后延迟执行,不阻塞页面渲染 |
useLayoutEffect | 同步执行:在DOM更新后、浏览器绘制前立即执行,会阻塞渲染 |
✅ 执行流程图示:
DOM 更新→useLayoutEffect 执行→ 浏览器绘制 →useEffect 执行
使用场景
| 钩子 | 适用场景 | 不适用场景 |
|---|---|---|
useEffect | - 数据获取 - 订阅事件 - 手动操作全局对象如 window- 无需同步更新DOM的操作 | 需要根据DOM计算并同步更新样式/布局 |
useLayoutEffect | - 读取/修改DOM布局(如元素尺寸、位置) - 避免视觉闪烁(如调整元素样式) - 同步更新状态以影响渲染 | 耗时操作(会阻塞渲染) |
性能影响
useEffect: 异步执行,不会阻塞渲染,适合大多数副作用操作useLayoutEffect: 会阻塞浏览器渲染,避免在内部执行耗时操作(如大量计算、网络请求等),否则会导致页面卡顿
服务端渲染SSR
useEffect: 在服务端渲染中可安全使用useLayoutEffect:
在SSR中会触发React警告(因为服务端无DOM)。解决方案:
// 在SSR中降级为useEffect
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect :useEffect;
代码示例
const Component = () => {
const [width, setWidth] = useState(0);
const divRef = useRef();
// 可能闪烁
useEffect(() => {
// 此时浏览器已绘制,用户可能先看到0px 再看到100px
setWidth(divRef.current.offsetWidth);
}, []);
// 无闪烁
useLayoutEffect(() => {
// 在绘制前同步更新,用户直接看到100px
setWidth(divRef.current.offsetWidth);
}, []);
return <div ref={divRef}>Width: {width}px</div>;
}
总结:如何选择?
根据经验,默认优先使用useEffect;仅当需要同步测量/修改DOM且遇到视觉问题时,才改用useLayoutEffect。
为什么hook不能写在条件语句里?
React内部通过调用顺序来跟踪每个hook的状态。如果我们将hook放在条件语句、循环/嵌套函数中,可能会导致hook的调用顺序在渲染之间发生变化,从而引发难以追踪的bug。
React Hooks必须无条件、按固定顺序调用,这是由React内部实现机制决定的。根本原因在于React依赖Hook的调用顺序来跟踪状态,具体原理如下:
顺序索引跟踪状态「核心原因」
React内部使用调用顺序索引来关联hook及其状态。每次渲染时,React会:
- 按顺序创建一个hook状态链表
- 用索引值(如
0,1,2,...)标识每个hook - 下次渲染时严格依赖相同的调用顺序匹配状态
条件语句如何破坏状态?
当hook放在条件语句中时,会导致调用顺序在不同渲染间不一致
const Component = () => {
if (condition) {
useEffect(..); // index 0
}
useEffect(..); // index 0/1
useEffect(..); // index 1/2
};
破坏原理
- 状态错位
第二个useEffec在condition为false时变成索引0,错误匹配了第一次渲染索引0的状态 - 状态丢失
条件跳过的Hook会导致后续所有Hook索引偏移 - 副作用错乱
useEffect/useLayoutEffect的清理和触发时机可能错配
正确方案
将条件判断移到Hook内部:
useEffect(() => {
if (condition) {
// ..
}
}, [condition]);
React的防护机制
开发模式下React会:
- 检测渲染间Hook数量变化
- 抛出错误:
Rendered fewer/more hooks than expected - 提示:
React Hookd must be called in the exact same order
优势
- 状态确定性:组件行为可预测
- 自动清理:保证副作用清理函数可靠执行
所有Hook调用必须无条件地置于组件顶层。