Linaria等零运行时库通过构建阶段静态分析CSS模板字面量,将可求值样式转为独立CSS文件,运行时仅应用类名,彻底消除insertRule等同步开销。

运行时 CSS-in-JS 开销无法靠“禁用某项配置”一键消除,必须分场景拆解:静态样式走编译时提取,动态部分做缓存+安全注入,高频更新组件还得绕开 JS 注入本身。
用 Linaria 或 vanilla-extract 替代 Emotion/styled-components
Linaria 的 outputCss: true 是硬性开关——关了就退化成普通运行时库,和 Emotion 没区别。它不支持 ${props => props.active && 'color: red'} 这类纯运行时插值,但换来的是零客户端 JS 样式计算。vanilla-extract 同理,所有样式必须写在 .css.ts 文件里,构建时转成原子类 + CSS 文件,连 useInsertionEffect 都不需要。
- 检查构建产物:确认
dist/assets/linaria.css存在且被 HTML 正确引入(Vite 不自动加<link>) - 动态逻辑只能靠 CSS 变量:比如
style={{ '--text-color': props.color }},再在 Linaria 里写color: var(--text-color, #000) - 不要试图在 Linaria 中用
if判断生成不同规则——构建阶段无法执行运行时逻辑
高频渲染组件中禁用 runtime 插入,改用预置 class
滚动卡片、实时图表这类组件,每帧都触发 useInsertionEffect + insertRule,内存分配和样式表重排压力远超预期。真实项目里更稳妥的做法是提前定义好有限状态的 class 名,用 className 切换而非运行时写样式。
- 把
color、opacity、transform等高频变更属性抽成原子类(如text-red-500、opacity-75),用 Tailwind 或自建原子 CSS 系统提供 - 完全避开 JS 控制伪类:hover/focus 等必须由真实 CSS 规则承载,内联 style 或 runtime 插入都无法触发
- 若必须响应 props 计算样式,优先用
transform和opacity——它们走合成层,不触发布局重排
服务端渲染必须隔离 cache 实例并提取 critical CSS
SSR 场景下样式丢失不是“没生效”,而是服务端生成的 class 名(如 css-1a2b3c)和客户端 hydration 时生成的不一致,导致样式未命中。关键不在“用了什么库”,而在 cache 生命周期是否可控。
立即学习“前端免费学习笔记(深入)”;
- 服务端每个请求都要新建
createCache({ key: 'ssr' }),不能复用全局单例 - 必须调用
renderStylesToString(cache)获取字符串,并插入到 HTML<head>的<style>标签中 - 客户端
hydrateRoot前,要用同一个key初始化CacheProvider,否则 class 名哈希对不上 - 检查 Network 面板返回的 HTML,确认
<style data-emotion="ssr">存在且内容非空
动画一律提至外部 .css 文件定义 @keyframes
任何 CSS-in-JS 库在运行时注册 @keyframes,都会导致浏览器反复解析动画规则,尤其在组件频繁挂载/卸载时。这不是缓存能解决的问题,而是机制层面的冗余。
- 新建
src/animations.css,写@keyframes slideIn { from { opacity: 0; } to { opacity: 1; } } - JS 中只用
animation: slideIn 0.3s ease-out引用,不要用模板字符串拼@keyframes - 确保该 CSS 文件被主入口引用(如
import './animations.css'),否则打包可能剔除 - 注意:CSS Modules 或
*.module.css中的@keyframes仍会被局部作用域化,需放在全局 CSS 文件中
真正难处理的从来不是“怎么写动态样式”,而是判断哪些本就不该动态——比如 padding、border-radius、字体层级,这些在构建时就能确定,却常被塞进 runtime 函数里反复计算。性能优化的起点,是承认 CSS 本就是声明式语言,不该被 JavaScript 过度中介。



















