从实战出发:CSS-in-JS 与传统 CSS 的取舍之道
背景和为什么写这篇文章?

最近几年,前端技术的发展速度可以用“飞快”来形容。特别是在样式管理这一块,CSS-in-JS 这种方案越来越受欢迎,甚至一度被视为未来趋势。不过呢,作为一个在一线开发前线摸爬滚打了六七年的老前端,我一直在纠结一个问题——到底该不该用 CSS-in-JS?
记得去年我们团队接手一个大型重构项目,原系统是典型的 SPA 架构,使用的是 Vue + 传统的 SCSS 模块化方式。代码库已经积累了五六年,组件数量庞大、样式文件冗余度高,样式覆盖问题层出不穷。就在那个节点上,有人提出了引入 styled-components(CSS-in-JS 方案之一) 来统一管理组件的样式。
这个提议当时在团队里掀起了不小的争议。支持者认为能提升开发体验、隔离样式、实现动态主题;反对者则担心性能、调试复杂、学习成本高等问题。最终,我们选择了折中:小模块试点 CSS-in-JS,同时保留传统 CSS 的主流程逻辑。
这段经历让我意识到,没有哪个方案是绝对的好或坏,只有合适不合适的场景。今天,我想结合实际项目经验,谈谈我对 CSS-in-JS 与传统 CSS 的思考,希望对正在做选择的你有所帮助。
遇到的问题和挑战

场景一:样式污染,无从下手
在之前一个电商后台管理系统中,我们采用的是 CSS Modules(BEM 命名规范),但仍然避免不了样式的相互影响。特别是在多个开发者并行开发的情况下,命名冲突屡见不鲜。比如:
/* ProductCard.module.css */
.product-card {
padding: 12px;
}
另一个同事在开发促销页面时,也写了类似的结构:
/* PromoCard.module.css */
.product-card {
padding: 20px;
}
结果两个组件都用了 .product-card 类名,但由于 Webpack 打包顺序的不同,有时候一个是 ProductCard__product-card___abc123,另一个是 PromoCard__product-card___def456,导致显示效果不可预测。这种“看似独立”的模块化方案,实际上在某些场景下还是存在潜在风险。
而且,调试非常费劲。你需要打开浏览器开发者工具,定位到底是哪一套类名生效了,还得回溯源码才能找到是哪一个模块引入的问题。这无疑增加了维护成本。
场景二:主题切换难以维护
另一个问题是:我们的产品需要支持用户自定义主题,比如换肤功能。原本是通过 SCSS 全局变量控制颜色、字体等属性,但在多品牌部署时,每次修改都需要重新编译打包。
这显然不够灵活。虽然我们尝试过使用 CSS 自定义属性(--theme-color)来解决,但受限于旧版浏览器兼容性问题,部分客户反馈页面样式错乱。
于是乎,团队开始调研能否借助 CSS-in-JS 的能力,实现在运行时动态注入样式的可能性。
解决思路:CSS-in-JS vs 传统 CSS 如何取舍?

我们的实践过程
我们选择了一个业务相对独立的子系统作为试点:数据分析仪表盘。该模块涉及大量动态可视化组件,且 UI 复杂度高,非常适合探索新方案。
✅ 尝试 CSS-in-JS(styled-components)
我们在 React 环境中引入了 styled-components,并尝试重构一些核心组件。
举个简单的例子,原来的按钮组件大概是这样写的:
// Button.jsx
import styles from './Button.module.css';
function Button({ children }) {
return (
<button className={styles.button}>
{children}
</button>
);
}
对应的 CSS 文件中有一个 .button 类定义。
换成 styled-components 后,写法变成了:
// Button.styled.js
import styled from 'styled-components';
export const Button = styled.button`
background-color: ${props => props.theme.primaryColor};
color: white;
padding: 10px 20px;
border-radius: 4px;
`;
然后我们在 App 中注入主题:
// App.jsx
import { ThemeProvider } from 'styled-components';
const theme = {
primaryColor: '#3f51b5',
};
function App() {
return (
<ThemeProvider theme={theme}>
<Button>提交</Button>
</ThemeProvider>
);
}
这样的好处显而易见:
- 样式和组件逻辑耦合更紧密
- 可以基于 props 动态计算样式
- 天然支持主题变量,方便全局定制
不仅如此,我们还发现组件复用变得更简单了。比如我们可以轻松封装一个带悬停动画的按钮组件:
export const HoverButton = styled(Button)`
&:hover {
opacity: 0.8;
transform: scale(1.03);
}
`;
这种继承方式比传统 CSS 更加直观,也不容易产生副作用。
❌ 回归传统 CSS 的原因
尽管 CSS-in-JS 看起来很优雅,但我们也不是一路走到底。在后续的一些模块开发中,特别是以下几种情况,我们又悄悄地“退回到传统 CSS”。
1. 静态页面样式多但交互少的场景
比如一个帮助文档页面、FAQ 页面,内容以静态展示为主。使用 CSS-in-JS 的话反而显得有点重,还会增加首屏加载时间。这时候我们就直接回到了熟悉的 CSS Modules,配合 PostCSS 插件生成 class 名。
2. 第三方库样式覆盖困难
我们集成了一些图表库,如 ECharts 和 D3。这些库生成的 DOM 是动态插入的,很难用 styled-components 统一管理样式。最终我们还是回归到了全局的 SCSS 变量配置,并结合 BEM 规范进行微调。
3. SEO 或 SSR 友好性考量
对于我们面向搜索引擎的产品页面,SEO 至关重要。CSS-in-JS 在服务端渲染时会带来一些水合(hydration)相关的问题。虽然大多数主流框架(如 Next.js)已经做了优化处理,但仍需额外关注样式是否正确注入,否则可能造成 FOUC(Flash of Unstyled Content)。
实际效果和收益对比
经过几个月的摸索,我们团队总结出了一套比较清晰的选型标准,具体如下:
| 维度 | CSS-in-JS | 传统 CSS |
|---|---|---|
| 开发体验 | ✅ 组件级作用域,减少冲突 | ❗ 类名难记,易污染 |
| 主题定制 | ✅ 支持动态主题 | ❗ 需要编译时预设 |
| 调试体验 | ⚠️ 样式名被 hash 化,调试略麻烦 | ✅ 直接对应源文件 |
| 性能表现 | ❗ JS 生成样式有额外消耗 | ✅ 静态文件加载快 |
| 学习成本 | ❗ 新人理解成本较高 | ✅ 技术栈更基础 |
| SSR/SEO 友好性 | ⚠️ 需配合处理样式注入 | ✅ 更稳定 |
我们最终的做法是 按场景混合使用,比如:
- 应用级组件、可复用 UI 模块(如 Modal、Table、Select)使用 CSS-in-JS
- 静态页面、营销类落地页采用 CSS Modules + PostCSS
- 图表组件、第三方插件依旧使用 SCSS 配置 + BEM
此外,在构建层面我们也做了一些优化:
- 使用
emotion替代styled-components(后者体积略大) - 对 CSS-in-JS 生成的样式表进行了压缩和提取
- 为不同入口设置不同的样式注入策略(如只在客户端使用 styled)
效果方面,整体代码可读性和维护性都有明显提升,尤其是在团队协作中大大减少了样式冲突问题。
经验分享和建议
🛠 个人踩过的坑和建议
1. 别为了用新技术而用新技术
我在早期阶段犯过一个错误:为了追求“现代化”,硬生生把所有的组件都转成了 styled-components,结果导致打包后 JS 文件增大了将近 20%。后来我们做性能优化时才发现问题所在,只好部分改回来。
所以我的建议是:先从小型组件开始试水,观察性能变化再决定是否全面推广。
2. 调试工具要跟上
如果你决定用 CSS-in-JS,一定要配上相应的调试工具,比如:
- Chrome DevTools 的样式面板(注意查看 computed style)
- VSCode 插件:VSCode-styled-components,支持语法高亮和智能提示
- 使用
jest-styled-components进行单元测试中的样式断言
这些都能显著提升你的调试效率。
3. 注意浏览器兼容性
虽然大多数 CSS-in-JS 方案都兼容现代浏览器,但在一些老旧设备(比如 Android 5.x、IE 11 已弃用)上仍可能出现样式异常。我们曾遇到一个 Bug,在低端安卓机器上,style 标签插入失败导致页面完全无样式。
因此,如果你的产品还有一定比例的老版本用户访问,一定要:
- 做兼容性测试
- 设置 fallback 机制(如提供纯 CSS 的降级版本)
4. 性能监控不能忽视
在 CSS-in-JS 引入前后,我们使用 Lighthouse 分别测试了首页的性能评分。起初确实出现过 TTI(Time to Interactive)下降的情况,后来通过懒加载部分组件、抽取关键样式等方式才得以优化。
结语:适合自己的才是最好的
写到这里,其实我已经说了不少了。但最关键的一点还是要记住:没有银弹,没有万能方案。
我在很多项目中见过团队因为一股脑追求“最流行”的解决方案,而忽略了实际业务需求,结果陷入性能瓶颈或者维护泥潭。
CSS-in-JS 和传统 CSS 各有千秋,关键在于你的项目特性、团队背景以及长期维护目标。
- 如果你是创业团队,注重快速迭代和组件复用,不妨大胆尝试 CSS-in-JS。
- 如果你是一个庞大的 legacy 项目,维护优先级高于创新,那就继续用你熟悉的方式。
- 最理想的状态是:两者并存,按需选择,灵活切换。
当然,最重要的是:保持开放心态,持续学习,根据真实反馈调整技术方案。
👾 附加彩蛋:个人偏爱的小工具推荐
最后,分享几个我在日常开发中常用的样式相关工具,亲测好用:
- Styleguidist:本地组件文档站点,支持实时样式预览
- Polished:针对 CSS-in-JS 的工具函数集合,比如颜色转换、透明度设置等
- Tailwind CSS:虽然不属于本文讨论范围,但我必须提一句——在不需要深度定制的场景中,它是效率神器
- Chrome devtools snippets:写一小段 JS 片段自动提取当前元素的所有内联样式,方便复用
如果你也有类似的经历或者正在做类似的技术决策,欢迎留言交流~咱们一起聊聊前端那些事 😄

评论 0