CSS-in-JS 还是传统 CSS?我在现代样式方案上的实战思考

林洋
2025-06-23 09:18
阅读 397

开篇:为什么我开始重新思考样式管理方式

在我五年前刚入行前端开发时,CSS 的写法还很“传统”:项目结构清晰、文件命名讲究,.module-name__element--modifier 这种 BEM 写法几乎是标配。大家在 assets/css 文件夹里小心翼翼地维护着一堆 .scss 文件,生怕样式污染或冲突。

而这几年随着 React 生态的蓬勃发展,CSS-in-JS 作为一种新兴的样式方案逐渐流行起来,从 styled-componentsemotion,再到如今的 styled-jsxLinaria,技术圈对样式的写法开始有了更强烈的争论。我所在的公司也经历过多次样式架构的调整和重构,今天我想结合自己亲身参与的几个真实项目,谈谈我对 CSS-in-JS 与传统 CSS 方案 的一些实战体会和选择建议。

移动端适配方案-1


问题描述:一次样式冲突引发的灾难级回归

去年我参与了一个中大型后台管理系统项目(以下简称 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 + 设计系统 易于协作和维护

给你的几点小建议:

  1. 不要为了炫技而换方案,CSS-in-JS 并不是银弹,它解决的只是特定问题;
  2. 尽早建立设计系统和主题机制,无论用哪种方式,都能让样式更具一致性;
  3. 工具链要简单稳定,尤其是构建阶段,尽量减少对打包工具的侵入;
  4. 保持团队认知一致,不同成员对 CSS 方案的认知差异越大,越容易出问题;
  5. 多用工具辅助开发,比如 VSCode 的 Emmet、Chrome 的样式检查器,以及 Emotion 自带的调试插件。

结语:没有最好的方案,只有最合适的方案

在我这几年的工作经历中,CSS 的写法一直在变,但有一点始终没变:关注用户体验的本质需求。无论是传统的 SCSS,还是最新的 CSS-in-JS,最终目标都是为了让界面更美观、交互更流畅、代码更容易维护。

如果你正在为自己的下一个项目选择样式方案,不妨先问自己几个问题:

  • 它需要频繁复用吗?
  • 是否涉及 SSR 或性能敏感场景?
  • 有没有严格的组件封装需求?
  • 团队的技术栈和习惯是否匹配?

只有真正了解自身项目的特点,才能做出最适合的技术选型。别怕尝试新东西,也别盲目跟风,找到那个“刚刚好”的平衡点,才是真正的工程智慧。


P.S. 最近我也在尝试把 Tailwind 与 CSS-in-JS 结合使用,在某些场景下效果非常棒。有兴趣的朋友欢迎留言交流,一起探索更多可能性 😊

评论 0

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