CSS-in-JS 还是传统 CSS?我在现代样式方案上的实战思考
开篇:为什么我开始重新思考样式管理方式
在我五年前刚入行前端开发时,CSS 的写法还很“传统”:项目结构清晰、文件命名讲究,.module-name__element--modifier 这种 BEM 写法几乎是标配。大家在 assets/css 文件夹里小心翼翼地维护着一堆 .scss 文件,生怕样式污染或冲突。
而这几年随着 React 生态的蓬勃发展,CSS-in-JS 作为一种新兴的样式方案逐渐流行起来,从 styled-components 到 emotion,再到如今的 styled-jsx 和 Linaria,技术圈对样式的写法开始有了更强烈的争论。我所在的公司也经历过多次样式架构的调整和重构,今天我想结合自己亲身参与的几个真实项目,谈谈我对 CSS-in-JS 与传统 CSS 方案 的一些实战体会和选择建议。

问题描述:一次样式冲突引发的灾难级回归
去年我参与了一个中大型后台管理系统项目(以下简称 A 项目),该项目最初采用的是 SCSS + BEM 的经典做法,组件复用性要求高,样式隔离性也很关键。
起初一切顺利,直到某天一位新来的同学引入了一个第三方 UI 组件库,并在全局样式中不经意加了一段:
.button {
background: red;
}
这个 .button 类几乎在每个页面中都有使用,导致所有按钮瞬间变红。这不仅影响了我们自己的组件样式,甚至改变了第三方组件的行为——最糟的是,这种全局污染很难通过代码审查发现。
那次事故之后,团队花了整整两天排查,最终决定尝试一种新的样式管理方式:CSS-in-JS,希望借助其天然作用域隔离的能力,提升组件样式的可维护性和稳定性。
解决方案:从 styled-components 到 Emotion 的选型之路
我们在 A 项目的中期改用了 styled-components,但很快发现了一些问题:
- 服务端渲染(SSR)支持不够友好,需要额外插件处理动态注入样式。
- 性能瓶颈:大量组件使用
styled()创建带样式的元素后,首次加载时间明显增加。 - 调试体验一般:DevTools 中的类名是一串哈希值,不利于快速定位问题。
于是我们切换到了 Emotion,因为它:
- 原生支持 SSR;
- 性能优化做得更好;
- 支持对象风格和字符串模板两种写法;
- 可以很好地和 Tailwind 等工具共存。
最终我们在项目中采用了 Emotion 的 @emotion/react 插件进行组件样式定义,例如:
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
const buttonStyle = css`
padding: 10px 20px;
border-radius: 4px;
background: #007bff;
color: white;
cursor: pointer;
&:hover {
background: #0056b3;
}
`;
function PrimaryButton({ children }) {
return <button css={buttonStyle}>{children}</button>;
}
这样一来,样式逻辑完全封装在组件内部,不再担心类名污染的问题,同时也提高了组件的可移植性和复用率。
踩坑经验:这些坑我都踩过,希望你不用再踩
1. 动态样式传参不好做
刚开始用 CSS-in-JS 时,我们经常遇到这样的场景:某个按钮的颜色需要根据用户角色动态变化。最初的写法类似这样:
const getButtonColor = (role) => {
switch(role) {
case 'admin': return '#dc3545';
case 'editor': return '#ffc107';
default: return '#007bff';
}
};
const dynamicStyle = (theme, role) => css`
background: ${getButtonColor(role)};
`;
结果发现,每次 role 变化,组件都会创建一个新的样式规则并插入 <head>,时间一久页面中堆满了无用的 <style> 标签。
后来我们改成缓存常用样式,或者干脆将状态控制移到 JS 层,只让样式依赖 props 的少量组合,避免重复创建样式规则。
2. DevTools 查看样式不方便
虽然 CSS-in-JS 可以实现局部作用域,但在浏览器开发者工具里看到的类名全是像 _8dpk94e 这样的哈希,调试的时候很不方便。
我们尝试了几种方法来缓解这个问题:
- 使用
label注释样式片段(在支持的环境中有效); - 编写统一的命名约定,比如
BtnPrimary,CardHeader; - 使用 Emotion 提供的
useTheme配合设计系统变量,提高语义表达能力; - 借助 Chrome 的「Computed」标签反向查找对应的组件位置;
尽管仍然不如传统类名那样直观,但配合良好的组件命名和样式组织,已经足够应对大多数调试场景。
效果总结:CSS-in-JS 带来的收益和代价
在引入 CSS-in-JS 后,A 项目的样式稳定性显著提升:
- 不再出现“样式被覆盖却不知道是谁干的”这类问题;
- 组件拆分和复用变得更加自然;
- 团队新人上手速度加快,因为样式和结构紧密耦合;
- 构建过程中可以更好地做按需加载和 Tree Shaking。
当然也有一些代价:
- 包体积略大(特别是使用 runtime 方案时);
- 有轻微的运行时性能开销;
- 如果滥用嵌套、媒体查询等,会导致样式难以维护;
- 某些旧版浏览器兼容性略差(主要是某些特性如
:where()无法 polyfill)。
整体来看,利大于弊。特别是在 React + TypeScript 的生态下,CSS-in-JS 更容易做到类型安全和样式即函数。
我的经验分享:如何做出适合自己的选择?
在多个项目中反复折腾 CSS 方案后,我总结出几个选择样式的“决策标准”,供大家参考:
| 场景/需求 | 推荐方案 | 原因说明 |
|---|---|---|
| 小型静态网站 | 传统 CSS / SCSS | 简单易上手,不需要太多配置 |
| 快速 MVP / 原型开发 | Tailwind + CSS Modules | 组合自由,效率高 |
| 复杂组件库 / 设计系统 | CSS-in-JS(推荐 Emotion 或 Linaria) | 样式封装强、可复用性高 |
| SEO / SSR 强依赖项目 | CSS-in-JS + Prevalence | 减少 FOUC 和重绘 |
| 多人协作、长期维护项目 | CSS Modules / CSS-in-JS + 设计系统 | 易于协作和维护 |
给你的几点小建议:
- 不要为了炫技而换方案,CSS-in-JS 并不是银弹,它解决的只是特定问题;
- 尽早建立设计系统和主题机制,无论用哪种方式,都能让样式更具一致性;
- 工具链要简单稳定,尤其是构建阶段,尽量减少对打包工具的侵入;
- 保持团队认知一致,不同成员对 CSS 方案的认知差异越大,越容易出问题;
- 多用工具辅助开发,比如 VSCode 的 Emmet、Chrome 的样式检查器,以及 Emotion 自带的调试插件。
结语:没有最好的方案,只有最合适的方案
在我这几年的工作经历中,CSS 的写法一直在变,但有一点始终没变:关注用户体验的本质需求。无论是传统的 SCSS,还是最新的 CSS-in-JS,最终目标都是为了让界面更美观、交互更流畅、代码更容易维护。
如果你正在为自己的下一个项目选择样式方案,不妨先问自己几个问题:
- 它需要频繁复用吗?
- 是否涉及 SSR 或性能敏感场景?
- 有没有严格的组件封装需求?
- 团队的技术栈和习惯是否匹配?
只有真正了解自身项目的特点,才能做出最适合的技术选型。别怕尝试新东西,也别盲目跟风,找到那个“刚刚好”的平衡点,才是真正的工程智慧。
P.S. 最近我也在尝试把 Tailwind 与 CSS-in-JS 结合使用,在某些场景下效果非常棒。有兴趣的朋友欢迎留言交流,一起探索更多可能性 😊

评论 0