从传统CSS到CSS-in-JS:我的样式方案成长之路
作为一个有五年经验的前端工程师,我经历过 CSS 的“刀耕火种”年代,也见证了前端工程化、组件化的飞速发展。今天想和大家聊聊我在项目中使用 CSS 和 CSS-in-JS 的真实经历,分享一些踩过的坑和总结出来的经验,帮助你在面对现代样式方案选择时更从容。
这篇文章将围绕一次真实的项目重构过程展开,希望能用具体的例子、实际的挑战以及解决思路,带大家看清传统CSS与CSS-in-JS各自的优劣。
背景:一次大型项目的重构尝试


几年前我参与了一个公司内部的CRM系统重构项目,这套系统已经上线好几年了,前端代码结构略显陈旧,样式管理比较混乱。原项目是 Vue2 + jQuery 混合开发,样式以全局 style.css 文件为主,辅以少量 SCSS 模块。随着功能不断叠加,CSS 冲突频繁出现,维护起来越来越吃力。
比如:
- 同一个
.btn类被多个模块复用,修改一处就会引发其他页面布局错乱 - 不同业务线各自定义了类似的 class 名,但样式表现不一致
- 大量内联样式嵌入模板,查找修改困难
- 组件层级复杂后,样式污染难以控制
这种背景下,我们决定在新版本中采用 Vue3 + Vite 技术栈重构整个项目,并引入现代样式方案来进行更好的样式管理。而这个选择点上,就是我们要讲的重点——如何在传统CSS与CSS-in-JS之间做权衡与取舍。
问题描述:传统CSS的困扰

在早期阶段,我们沿用了传统的 CSS 模块化写法。每个 Vue 组件配一个 .scss 文件,并通过命名规范(如 BEM)来避免冲突,同时也做了基础组件抽象。
看起来逻辑很清晰,但很快我们就遇到了几个典型问题:
1. 样式作用域失控
虽然我们在组件里写了 scoped 样式,但在某些嵌套较深的组件中,父级的样式规则意外影响到了子组件,尤其是第三方组件库的内容也被污染。
<style scoped>
.btn {
background: red;
}
</style>
但如果你用了某个 UI 库中的按钮组件,结果它的类名也是 .btn,这时候 scoped 并不能阻止样式污染。
2. 命名冲突与重复劳动
不同团队成员在不同组件中各自定义了类似的 class 名,比如 .form-control, .input-wrapper 等等。最终这些 class 在打包后会合并成一个文件,导致样式不可控,还增加了冗余代码量。
3. 缺乏动态能力
有些组件需要根据状态改变颜色、布局方式或者动画节奏,这时候传统CSS就显得无能为力。只能配合 JS 动态生成 class 或操作 style 属性,不仅耦合度高,也不易维护。
4. 难以测试和调试
传统 CSS 的调试依赖浏览器 DevTools,很难像 JS 一样做单元测试或快照比对,样式错误往往只有在 UI 上出现问题时才被发现。
解决方案探索:为什么选择CSS-in-JS?

带着这些问题,我们开始调研新的样式解决方案。当时主流的 CSS-in-JS 方案包括 styled-components(React 生态)、Emotion、Stitches,还有更适合 Vue 的 Vue Styleguidist 插件和一些社区生态的封装工具包。经过评估,我们选择了基于 emotion/css 实现的轻量级封装方案。
Emotion 入门小 Tip:对于 Vue 项目,可以使用
@emotion/css或者配合 Composition API 手动注入 styles,灵活度高且无需强绑定特定框架。
我们并没有直接选用完全集成式的方案(比如 Vue 中不太流行 styled-components),而是决定自己封装一套 CSS-in-JS 工具函数,核心目标是:
- 每个组件样式完全独立,防止污染
- 支持运行时动态样式计算
- 提高性能优化空间(如自动去重、提取关键 CSS)
- 减少人工命名负担,提升可维护性
具体实现思路

我们把 CSS-in-JS 分成三个层次来实践:
1. 核心工具层:封装 css() 方法
参考 Emotion 的风格,我们实现了一个简单的 css() 方法,接受对象参数并返回唯一的 class name。
import { css } from '@emotion/css'
const buttonStyle = css`
padding: 8px 16px;
border-radius: 4px;
transition: all 0.2s ease;
&:hover {
background-color: #f5f5f5;
}
${props => props.primary ? `
background-color: #0d6efd;
color: white;
` : ''}
`
然后在模板中使用:
<template>
<button :class="buttonStyle">提交</button>
</template>
<script setup>
import { buttonStyle } from './styles'
</script>
2. 组件层面封装动态行为
结合 Vue 的响应式机制,我们可以轻松地让样式随状态变化而更新:
function useButtonStyles(isLoading) {
return computed(() => css`
opacity: ${isLoading.value ? 0.6 : 1};
cursor: ${isLoading.value ? 'not-allowed' : 'pointer'};
`)
}
这样就能非常优雅地处理 loading 状态下的交互反馈了。
3. 构建期优化策略
为了保证生产环境性能,我们在构建流程中加入了一些优化措施:
- 使用
babel-plugin-emotion自动提取静态样式,减少运行时计算 - 对样式字符串进行 hash 化命名,确保唯一性
- 引入 PostCSS 插件自动加前缀,保证浏览器兼容性
- 开发环境下保留源码映射,便于调试;生产环境合并压缩样式
效果总结:从混乱到可控
重构完成后,我们在以下几个方面取得了显著改善:
✅ 更好的隔离性
- 样式污染几乎消失,即使是多级嵌套组件也不会互相干扰
- 第三方组件样式不再受本地 CSS 波及
✅ 动态样式得心应手
- 状态驱动的 UI 变化更加直观简洁
- 响应式设计可以通过 JS 控制器统一处理,而不是分散在多个媒体查询中
✅ 开发效率提升
- 无需纠结命名规则和命名冲突
- 样式可以直接跟随组件定义,阅读代码时上下文连贯
- 修改样式更容易找到对应位置,不再需要反复切换 CSS 文件
✅ 性能优化空间更大
- 自动合并重复样式
- 利用 Tree-shaking 删除未使用的样式
- 支持关键 CSS 提取(Critical CSS)
我的一些建议和注意事项
这段经历让我对现代样式方案有了更深的理解,以下是一些给新手的建议:
🎯 什么时候适合使用CSS-in-JS?
- 组件化程度高的项目:尤其适用于 React/Vue 这样的声明式框架
- 需要高频动态样式的场景:比如图表、动画、主题定制
- 多人协作项目:避免 class 命名冲突、提升一致性
- 追求极致隔离的 UI 库或设计系统:确保组件样式不受外部干扰
⛔️ 什么时候慎用CSS-in-JS?
- SEO 至关重要的站点:运行时注入的样式不利于爬虫抓取
- 低性能设备支持场景:过多运行时 JS 渲染可能带来性能问题
- 已有大量传统 CSS 代码:迁移成本高,短期内收益不明显
- 极简 HTML/CSS 小型项目:没有必要引入额外依赖和复杂度
💡 小技巧和经验分享
- 命名不是万能的:即使使用 BEM,也无法完全避免样式冲突。真正的解耦在于作用域隔离。
- 工具链要跟上:CSS-in-JS 的性能优化离不开构建工具的支持。推荐和 PostCSS、Vite/Rollup 搭配使用。
- DevTools调试技巧:
- 浏览器审查元素时查看 class 名即可快速定位样式来源
- emotion 默认会注入 source map,在 Chrome DevTools 中能看到具体文件路径
- 不要过度封装:保持样式逻辑清晰,避免写出一堆嵌套条件判断导致难以理解。
- 注意 SSR 支持:服务端渲染项目要注意样式标签是否正确输出到 HTML,必要时使用
@emotion/server。
最后一点思考:技术选型没有绝对正确

在这次项目实践中,我也意识到:CSS-in-JS 并非银弹,它只是一个工具,真正决定成败的是我们如何理解和运用它。在不同的项目背景、团队文化甚至产品需求面前,我们可能需要灵活调整自己的样式方案。
比如我现在正在参与的一个开源项目,用户量不大但希望降低依赖和构建复杂度,我就又回到了“局部 scoped CSS + Tailwind CSS”的组合拳,效果也非常不错。
所以,回到文章开头的问题:“CSS-in-JS vs 传统CSS,怎么选?”
我的回答是:看需求、看团队、看未来。
- 如果你正在做一个组件化强、动态交互丰富的中大型应用,CSS-in-JS 是值得考虑的方向;
- 如果你的项目轻量简单,传统 CSS 搭配良好的组织结构也完全可以胜任;
- 如果你是团队的技术负责人,别忘了还要考虑其他人的学习成本和技术储备。
总之,工具服务于人,合适最重要。
结语:写给未来的自己
回想这五年的前端旅程,从最早手写纯 CSS,到现在自如运用 CSS-in-JS,我始终相信一件事:
前端工程师不仅要懂怎么写样式,更要懂得何时用什么方法写,才能既快又好地完成工作。
希望这篇来自实战的经验之谈,能帮你少走弯路,在面对样式方案选择时更有底气。
如果你也正走在前端进阶的路上,不妨多动手实践几种方案。唯有亲自踩过坑,你才会真正知道什么是好工具,什么是真需求。
前端世界的风景永远精彩,愿你我都能在代码中写出优雅与力量。
作者简介:5年前端开发,热爱组件化思维与用户体验,擅长 Vue/React 全栈开发。欢迎关注我的公众号 / GitHub 交流更多实战技巧。

评论 0