从传统CSS到CSS-in-JS:我的样式方案成长之路

数字游牧开发者
2025-06-25 07:10
阅读 215

作为一个有五年经验的前端工程师,我经历过 CSS 的“刀耕火种”年代,也见证了前端工程化、组件化的飞速发展。今天想和大家聊聊我在项目中使用 CSS 和 CSS-in-JS 的真实经历,分享一些踩过的坑和总结出来的经验,帮助你在面对现代样式方案选择时更从容。

这篇文章将围绕一次真实的项目重构过程展开,希望能用具体的例子、实际的挑战以及解决思路,带大家看清传统CSS与CSS-in-JS各自的优劣。


背景:一次大型项目的重构尝试

背景:一次大型项目的重构尝试

用户交互流程图-2

几年前我参与了一个公司内部的CRM系统重构项目,这套系统已经上线好几年了,前端代码结构略显陈旧,样式管理比较混乱。原项目是 Vue2 + jQuery 混合开发,样式以全局 style.css 文件为主,辅以少量 SCSS 模块。随着功能不断叠加,CSS 冲突频繁出现,维护起来越来越吃力。

比如:

  • 同一个 .btn 类被多个模块复用,修改一处就会引发其他页面布局错乱
  • 不同业务线各自定义了类似的 class 名,但样式表现不一致
  • 大量内联样式嵌入模板,查找修改困难
  • 组件层级复杂后,样式污染难以控制

这种背景下,我们决定在新版本中采用 Vue3 + Vite 技术栈重构整个项目,并引入现代样式方案来进行更好的样式管理。而这个选择点上,就是我们要讲的重点——如何在传统CSS与CSS-in-JS之间做权衡与取舍。


问题描述:传统CSS的困扰

问题描述:传统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?

带着这些问题,我们开始调研新的样式解决方案。当时主流的 CSS-in-JS 方案包括 styled-components(React 生态)、EmotionStitches,还有更适合 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 小型项目:没有必要引入额外依赖和复杂度

💡 小技巧和经验分享

  1. 命名不是万能的:即使使用 BEM,也无法完全避免样式冲突。真正的解耦在于作用域隔离。
  2. 工具链要跟上:CSS-in-JS 的性能优化离不开构建工具的支持。推荐和 PostCSS、Vite/Rollup 搭配使用。
  3. DevTools调试技巧
    • 浏览器审查元素时查看 class 名即可快速定位样式来源
    • emotion 默认会注入 source map,在 Chrome DevTools 中能看到具体文件路径
  4. 不要过度封装:保持样式逻辑清晰,避免写出一堆嵌套条件判断导致难以理解。
  5. 注意 SSR 支持:服务端渲染项目要注意样式标签是否正确输出到 HTML,必要时使用 @emotion/server

最后一点思考:技术选型没有绝对正确

前端开发工具界面-1

在这次项目实践中,我也意识到: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

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝