CSS-in-JS 与传统 CSS:我的现代样式方案选择实战指南

设计稿别变了
2025-06-23 09:29
阅读 610

引言:为什么我会去思考这个问题?

引言:为什么我会去思考这个问题?

大家好,我是张工,从事前端开发已经有七八年了。最近几年,我参与了一些中大型前端项目重构工作,也负责过多个从0到1的项目搭建。在这个过程中,有一个问题始终伴随着我:在现代前端项目中,到底该用 CSS-in-JS 还是传统的 CSS 解决方案?

起初我以为这是一个“伪命题”——毕竟不就是写个样式嘛,怎么方便怎么来。但随着团队人员变化、项目规模扩大、功能迭代频繁,我逐渐意识到,这其实是一个直接影响开发效率、可维护性和团队协作方式的技术选型问题。

今天我就结合几个真实项目经历,和大家一起聊聊我在不同项目场景下如何做这个取舍,以及踩过的坑、收获的经验,希望能对你有所帮助。


我遇到的第一个挑战:组件封装 + 团队协同带来的样式管理难题

我遇到的第一个挑战:组件封装 + 团队协同带来的样式管理难题

移动端适配方案-2

背景介绍

2021 年我加入了一个内部工具平台项目,目标是打造一个统一的 UI 组件库供公司多个业务线使用。一开始我们选择了标准的做法:CSS Modules + Sass。结构简单明了,每个组件目录里都有 index.jsstyle.module.scss 文件。

看起来很完美对吧?但没过多久就遇到了问题:

  • 不同开发人员写的类名冲突了(明明用了 CSS Modules!)
  • 样式覆盖严重,经常要手动添加 !important 或者提高优先级
  • 修改样式需要同步修改 .scss 文件和 JS 中引入的类名,容易出错
  • 新人上手慢,理解类名映射关系不够直观
  • 某些组件希望根据 props 动态调整样式,这时候传统的 CSS 写法显得力不从心

我们尝试的第一种方案:继续优化模块化体系

我们试过一些手段来缓解这些问题,比如更严格的命名规范、建立共享变量库、用 BEM 来提升类名可读性……但这些都治标不治本。

真正让我开始重新审视这个问题的,是当我们需要实现一个高度定制化的表格组件,它允许用户通过配置项动态控制列宽、颜色、行高、hover 效果等等。这个时候,单纯靠写死的 CSS 类已经很难满足需求了。

我开始调研市面上主流的 CSS-in-JS 方案:styled-components、emotion、goober……

最终我们选择了 emotion,因为它支持 React 的 @emotion/styled 语法,也能很好地配合 Typescript,而且性能在当时来看表现不错。


CSS-in-JS 是灵丹妙药吗?让我们用代码说话

CSS-in-JS 是灵丹妙药吗?让我们用代码说话

示例:传统 CSS 写法 vs CSS-in-Js 写法

假设我们要做一个按钮组件,支持传入颜色、大小、是否禁用等 props,并生成对应的样式。

传统 CSS + CSS Modules 写法

// Button.jsx
import styles from './Button.module.css'

function Button({ color = 'primary', size = 'medium', disabled, children }) {
  const className = `${styles.button} ${styles[color]} ${styles[size]}`
  return (
    <button className={className} disabled={disabled}>
      {children}
    </button>
  )
}
/* Button.module.css */
.button {
  padding: 8px 16px;
  border-radius: 4px;
}

.primary {
  background-color: blue;
  color: white;
}

.secondary {
  background-color: gray;
  color: black;
}

.medium {
  font-size: 14px;
}
.large {
  font-size: 16px;
}

这种方式的优点很明显:清晰易懂、利于调试。但缺点也很明显:

  • 样式定义和逻辑完全分离,容易迷失在文件夹之间
  • 随着状态增多,JSX 中的 class 拼接变得难以维护
  • 所有样式都要预先写好,无法根据 props 实时计算生成

Emotion styled API 写法

// Button.jsx
import styled from '@emotion/styled'

const StyledButton = styled('button')(
  {
    padding: '8px 16px',
    borderRadius: 4,
    fontSize: props => (props.size === 'large' ? '16px' : '14px'),
    backgroundColor: props => (props.color === 'primary' ? 'blue' : 'gray'),
    color: props => (props.color === 'primary' ? 'white' : 'black'),
  },
  props => props.disabled && {
    opacity: 0.5,
    pointerEvents: 'none'
  }
)

function Button({ color = 'primary', size = 'medium', disabled, children }) {
  return (
    <StyledButton color={color} size={size} disabled={disabled}>
      {children}
    </StyledButton>
  )
}

这段代码看起来是不是清爽很多?虽然写法有点“奇怪”,但优点显而易见:

  • 样式紧随组件定义,阅读体验更好
  • 可以直接使用 props 值动态生成样式值
  • 无需再维护一堆 .module.css 文件,减少文件数量
  • Emotion 会自动处理 classname 映射和缓存机制,不用担心冲突

使用 CSS-in-JS 遇到的坑

使用 CSS-in-JS 遇到的坑

别急,我也不是说 CSS-in-JS 就没有问题。在实际项目中我们确实踩了不少坑:

💣 性能问题(特别是在 SSR 项目中)

刚开始我们在一个 Next.js 项目的 SSR 页面中大量使用 emotion,结果构建速度骤降,首屏加载明显变慢。后来发现是因为:

  • 每个带有样式的组件都会注入 <style> 标签,SSR 阶段合并困难
  • 服务端渲染时 emotion 默认不会进行缓存,导致每次请求都需要重新生成样式

解决方案:

  • 升级 emotion 到 v11+,启用 @emotion/server 插件,在服务器端预提取所有样式标签
  • 配置 webpack 缓存策略
  • 在 Next.js 中使用官方推荐的 _document.tsx 注入方式

💥 可调试性问题

有些人说 CSS-in-JS 不如传统 CSS 容易调试。这话有一定道理。比如你看到页面上的某个元素样式不对,但在浏览器 DevTools 里只能看到类似 .css-1a2b3c 的类名,不知道具体来自哪个组件、哪条语句。

解决办法:

  • 开启 emotion 的 label 支持:label 选项可以让生成的类名包含组件名或样式对象的 key
  • 使用 Chrome 插件,比如 React Developer Tools,直接定位到对应组件
  • 对关键样式部分尽量命名,便于排查
const Container = styled.div({
  // 自动带标签
}, 'Container')

🧊 主题系统兼容性和嵌套太深的问题

我们一开始希望通过 emotion 构建一套主题系统,用来集中管理配色、字体、间距等设计变量。但在某些深度嵌套的子组件中访问 theme 出现了问题,主要是:

  • 子组件没有被正确包裹在 ThemeProvider 下
  • 状态传递中断或者未按预期解析

经验总结:

  • 确保整个应用都在 ThemeProvider 包裹范围内(通常是根组件)
  • 主题变量要扁平化设计,避免嵌套层级过深
  • 可以结合 ThemeProvider 与 context 机制做增强封装

我们也没有抛弃传统 CSS:合适才是最好的

可能你会问:“那你现在还用传统 CSS 吗?”

答案是:当然用啊!

举个例子:一个轻量级营销网站的静态页

去年年底我们接手了一个企业官网类项目,页面内容相对静态,SEO 要求较高,整体交互也不复杂。这时候我们果断放弃了 CSS-in-JS,选择了:

  • PostCSS + TailwindCSS(用于快速布局)
  • SCSS(用于部分自定义组件样式)
  • PurgeCSS(清理冗余类名)
  • autoprefixer(适配浏览器前缀)

这样的组合好处很明显:

  • 构建速度快,几乎没有运行时负担
  • SEO 表现优秀(CSS-in-JS 的 SSR 处理始终不如原生友好)
  • Tailwind 提供了极其丰富的 utility class,非常适合这种展示类页面
  • 所有样式都在构建阶段编译完成,生产环境性能极佳

而且说实话,对于这类偏静态的页面,CSS-in-JS 的优势其实并不明显


我是怎么做技术选型的?

说了这么多,我想你也看出来了:不存在绝对的对与错,只有“适合与否”。

在我现在的认知中,技术选型应该围绕以下几个维度展开:

维度 传统 CSS(包括 SCSS、CSS Modules) CSS-in-JS
项目类型 展示型页面、静态站点、SEO敏感型 动态性强的组件、SPA 应用
样式复杂度 静态样式为主,无太多条件分支 需要根据 props/状态动态生成样式
维护成本 文件略多,样式分散 样式集中,学习成本稍高
性能 构建阶段完成,运行时无开销 运行时注入样式,需注意 SSR 兼容
调试难度 可读性好,便于审查 类名抽象,依赖 DevTools
工具链成熟度 成熟稳定,社区资源丰富 生态活跃但仍有碎片化风险

前端开发工具界面-1

我的真实建议:

  • 如果你在做一个 企业官网 / 文档网站 / 登录页等展示性质强的内容页,那强烈建议使用传统 CSS(特别是 TailwindCSS + PostCSS 的组合),性能和 SEO 表现会让你惊喜。
  • 如果你在开发一个 复杂的 SPA 应用 / UI 组件库 / 动态数据驱动的交互界面,那 CSS-in-JS 会大大提升你的开发效率和可维护性。
  • 如果你是小团队创业项目,建议前期采用传统 CSS 快速开发,后期逐步过渡;如果是大厂或长期维护的项目,可以一开始就考虑引入成熟的 CSS-in-JS 方案。

最后想分享的几点经验

✅ 真实心得一:不要把 CSS-in-JS 当成炫技

我曾经在一个技术分享会上看到有人炫技似的写出了一堆嵌套函数、层层推导出来的样式属性,看似灵活,其实让人根本看不懂。代码的可读性和可维护性永远比“技巧酷炫”更重要。

✅ 真实心得二:不要忽视浏览器兼容性和性能表现

尤其是在国内环境下,IE11 或低版本手机浏览器仍然需要支持的情况下,CSS-in-JS 可能会有意想不到的表现。一定要做好兜底测试。

✅ 真实心得三:文档 & 规范才是团队协作的核心

无论你选哪种方案,都需要制定良好的编码规范,并保持一致的风格。否则哪怕你用的是最流行的 styled-components,项目也会很快陷入混乱。


总结:选择没有标准答案,关键是理解和匹配

这篇文章写到这里,我想表达的核心其实只有一句话:

“没有银弹,只有因地制宜。”

技术的选择从来不是非黑即白的判断题,而是不断探索、试错、权衡的过程。

不管是传统 CSS,还是 CSS-in-JS,它们都只是工具。重点在于你能否清楚地理解它们的优劣势,并根据当前项目的特点做出最适合的选择。

如果你还在纠结样式该怎么写,不妨先试着回答这几个问题:

  1. 我正在做的这个项目是偏展示类的,还是交互性特别强?
  2. 是否需要频繁地基于 props 或 state 动态改变样式?
  3. 我的团队是否有能力维护较为复杂的样式工程方案?
  4. 是否对 SEO 或性能有特殊要求?

我相信只要你搞清楚这些问题,就能找到自己的答案。


如果你也在实际项目中做过类似的选型决策,或者想跟我交流某一种方案的具体落地细节,欢迎留言一起探讨。前端的世界一直在变,但我们的目标始终不变 —— 写出优雅、健壮、可维护的代码。

共勉 🙌

评论 0

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