useInsertionEffect是React 18专为CSS-in-JS设计的样式注入Hook,执行时机早于useLayoutEffect,在DOM插入前同步完成,可彻底避免FOUC和样式覆盖问题;而useLayoutEffect因在DOM变更后、绘制前执行,易导致样式延迟注入引发闪烁。

useInsertionEffect 是唯一安全的样式插入时机,其他 Hook 都会触发 FOUC 或顺序错乱。
useLayoutEffect 插入样式为什么会导致 FOUC
在并发渲染下,useLayoutEffect 执行时 DOM 已经完成变更,但浏览器尚未绘制 —— 这意味着组件内容已渲染到页面,而样式还没来得及注入。用户会短暂看到无样式的“裸内容”,尤其在快速导航或服务端渲染水合后更明显。
- 常见错误现象:
style元素晚于组件挂载几毫秒才插入,DevTools 中能看到元素先以默认样式出现,再突然变色/缩放 - 使用场景:emotion/styled-components 早期版本在
useLayoutEffect里调用insertCSS,升级 React 18 后集体出现闪烁 - 根本原因:React 18 的可中断渲染让
useLayoutEffect不再是“最早能操作 DOM 的位置”
useEffect 完全不适合样式注入
useEffect 在浏览器绘制之后执行,此时样式注入已毫无意义 —— 页面已经完成渲染,强行插入 <style></style> 只会触发重排重绘,且无法保证覆盖优先级。
- 常见错误现象:样式生效延迟、动态主题切换时旧样式残留、SSR 水合后闪白屏
- 参数差异:
useEffect依赖数组变化只影响执行频率,但完全无法解决“时机过晚”这个本质问题 - 性能影响:每次注入都可能引发 layout thrashing,尤其在列表滚动中频繁触发
useInsertionEffect 的实际约束条件
它不是万能的“样式钩子”,而是有明确边界的设计:只允许做两件事 —— 创建 <style></style> 节点、写入 textContent。任何读取 DOM 尺寸、调用 getComputedStyle 或依赖其他组件状态的操作都会出错。
立即学习“前端免费学习笔记(深入)”;
- 容易踩的坑:在
setup函数里访问document.body或尝试ref.current?.offsetHeight—— 此时 DOM 尚未生成,返回null或undefined - 正确做法:样式必须是纯静态字符串,或仅基于传入的
props/theme参数计算,不依赖真实 DOM - 示例片段:
useInsertionEffect(() => { const style = document.createElement('style'); style.textContent = `.button-${props.size} { padding: ${sizeMap[props.size]}; }`; document.head.appendChild(style); return () => document.head.removeChild(style); }, [props.size]);
真正难的是把“动态样式逻辑”剥离成纯计算 + 静态注入 —— 大多数 CSS-in-JS 库为此重写了核心注入层,不是简单替换 Hook 就能搞定的。


















