CSS-in-JS vs 传统CSS:现代样式方案选择指南

TypeScript守夜人
2025-12-16 06:29
阅读 311

上周五晚上十点半,深圳的夏天依旧闷热,我窝在出租屋里调试一个 React 组件的 hover 状态。AirPods 里放着 Lo-fi beats,VS Code 的终端正在跑着 npm run build,而我的 Mac 触控板已经烫到能煎蛋了。

作为一个在深圳远程办公的独立开发者(说得高大上点是“自由职业者”,说白了就是没人管、也没人陪),我最近接了个小产品外包项目——帮一个刚融完 A 轮的创业公司重构前端架构。他们之前用的是“祖传” jQuery + 全局 CSS,现在想上 React + TypeScript,顺便把样式管理现代化一下。

结果 PM 在飞书上甩来一句:“我们要支持动态主题切换,还要按用户角色展示不同 UI 风格。”
我当时就笑出了声——这不就是典型的“面试题挑战”照进现实吗?要知道,这种需求在 LeetCode 上可能只是道 medium 题,但在真实产品里,它能让你加班到怀疑人生。


为什么突然要聊这个?

其实早在去年双11期间,我就踩过一次坑。当时帮一家电商 SaaS 做后台系统,为了赶 deadline,我图快直接用了 styled-components,结果上线后发现首屏 LCP(Largest Contentful Paint)慢了近 300ms。运维老哥半夜打电话来:“兄弟,你这 JS 包里塞了 80KB 的 CSS,能不能砍一砍?”

那会儿我才意识到:CSS-in-JS 不是银弹,传统 CSS 也不是古董。选错方案,轻则性能翻车,重则简历上多一条“因样式架构导致线上事故”的黑历史。

所以今天这篇,不是教科书式的对比,而是我这两年在深圳远程开发中,被产品经理“折磨”、被浏览器兼容性“教育”、被 bundle size “暴打”后的实战总结。希望你下次面试被问到“你怎么选样式方案”时,能笑着掏出这段经验写进简历。


场景驱动:你的产品决定了技术选型

先说结论:没有绝对优劣,只有是否匹配产品阶段和团队能力

我在腾讯系公司待过几年(没错,就是那个深圳南山科技园人挤人的地方),大厂喜欢搞标准化、工程化,一套 Design Token + CSS Modules + PostCSS 流水线跑得稳如老狗。但你现在是个三个人的小创业团队?天天改 UI、换主题、AB 测试?那可能 Emotion 或 Styled Components 更香。

举个例子:我最近做的这个项目,要求“管理员看到蓝色主题,普通用户看到绿色,VIP 用户还能自定义颜色”。用传统 CSS 怎么搞?你得维护一堆 .theme-admin, .theme-user, .theme-vip 类名,再配合 CSS 变量动态注入。逻辑散落在 HTML、JS、CSS 三层,改个颜色要跨三个文件,测试同学都快疯了。

而用 CSS-in-JS,比如 Emotion,代码可以这样写:

// theme.ts
export const getTheme = (userRole: string, customColor?: string) => {
  if (customColor) return { primary: customColor };
  switch (userRole) {
    case 'admin': return { primary: '#1890ff' };
    case 'vip': return { primary: '#722ed1' };
    default: return { primary: '#52c41a' };
  }
};

// Button.tsx
import { css } from '@emotion/react';

const StyledButton = ({ theme }) => (
  <button
    css={css`
      background-color: ${theme.primary};
      border: none;
      padding: 8px 16px;
      border-radius: 4px;
      color: white;
      &:hover {
        opacity: 0.9;
      }
    `}
  />
);

是不是清爽多了?逻辑集中,主题切换只需传一个 theme prop,连产品经理都能看懂。


性能与体验:别让花哨害了用户

但别高兴太早。CSS-in-JS 的 runtime 开销是真的存在。我在 Chrome DevTools 里测过,Emotion 在 SSR 场景下比纯 CSS 多出约 50-100ms 的 hydration 时间。如果你的产品面向三四线城市用户,用的还是千元机 + 4G 网络,这点延迟可能就是跳出率飙升的元凶。

这时候就得权衡:动态性 vs 性能

我的策略是:核心页面用传统 CSS(或 CSS Modules),营销页/管理后台用 CSS-in-JS

比如登录页、首页这种对 SEO 和首屏速度敏感的地方,我会用 Sass + CSS Modules,配合 PurgeCSS 干掉无用样式。而内部管理系统,用户已经登录,设备性能较好,就可以放飞自我用 Emotion。

还有一点容易被忽略:浏览器开发者工具的调试体验

用 CSS Modules 时,生成的类名像 .Button_primary__abc123,虽然丑但至少能搜到。而某些 CSS-in-JS 库(尤其是旧版 styled-components)会在 <style> 标签里塞一大堆匿名规则,F12 打开一看全是 css-1x2y3z,想改个 margin 得靠猜。不过好消息是,Emotion 现在支持 label 选项:

css`
  label: "PrimaryButton";
  background: blue;
`

这样在 DevTools 里就能看到 .css-xxxxx.PrimaryButton,调试体验直线上升。


工程化视角:团队协作与长期维护

作为独立开发者,我经常一个人干全栈,但也会和别的 freelancer 协作。这时候样式方案的选择直接影响协作效率。

传统 CSS 最大的问题是全局污染。你写个 .container,隔壁同事也写个 .container,合并代码时互相覆盖,最后只能加 !important 对线。CSS Modules 通过局部作用域解决了这个问题,但代价是失去了全局样式的灵活性——比如你想统一修改所有按钮的圆角,得一个个组件去改。

CSS-in-JS 则天然隔离,每个组件的样式都是函数作用域内的变量,根本不会冲突。而且配合 TypeScript,还能做类型提示:

interface Theme {
  primary: string;
  borderRadius: number;
}

// 使用时自动补全
css`
  border-radius: ${props => props.theme.borderRadius}px;
`

这对写简历也很有帮助——面试官一听“我们用 Emotion + TypeScript 实现了类型安全的主题系统”,立刻觉得你工程素养不错。

不过要注意:CSS-in-JS 会增加 JS Bundle Size。我用 Webpack Bundle Analyzer 看过,引入 Emotion 后 vendor chunk 胀了约 15KB(gzip 后)。如果你的产品对加载速度极度敏感(比如 PWA 应用),这笔账得算清楚。


快速决策表:根据你的处境选方案

下面这张表是我结合自己踩坑经验整理的,你可以对照看看:

维度 传统 CSS / CSS Modules CSS-in-JS (Emotion/Styled)
动态主题 需配合 CSS 变量,逻辑分散 原生支持,逻辑集中
Bundle Size 极小(纯文本) 增加 10-20KB JS
SSR 性能 优秀(直接输出 <link> 中等(需序列化样式)
调试体验 类名可读(配合命名规范) 需配置 label,否则难读
TypeScript 支持 弱(需手动声明模块) 强(内联样式可类型检查)
学习成本 低(前端必会) 中(需理解 JS 作用域)
适合场景 营销页、官网、性能敏感产品 管理后台、SaaS、复杂交互应用

我的最终选择:混合策略

经过几轮折腾,我给这个创业公司定了个“混合方案”:

  • 公共基础样式(reset、grid、typography)用 Sass 编译成独立 CSS 文件,CDN 加载
  • 组件库用 Emotion 编写,支持动态主题
  • 构建时用 @emotion/babel-plugin 自动提取关键 CSS(Critical CSS)注入 HTML
  • 生产环境开启 speedy: true(使用 insertRule 而非 innerHTML)

效果如何?Lighthouse 分数从 68 提升到 89,产品经理再也不提“换个主题”这种需求了(笑)。


写在最后:技术选型的本质是权衡

说实话,作为一个孤独的远程开发者,我有时候会羡慕大厂同学——他们有基建团队兜底,有设计系统支撑,选什么方案都有文档可循。而我得自己权衡每一 KB 的体积、每一毫秒的延迟。

但这也正是乐趣所在。当你在深夜 fix 掉一个样式闪烁 bug,看到 Lighthouse 报告里绿油油的分数,那种成就感,比刷十道 LeetCode 还爽。

所以,别再纠结“哪个更好”了。问问你的产品处于什么阶段,你的用户用什么设备,你的团队能维护什么复杂度。选一个当下最合适的,然后快速迭代。

毕竟,我们的目标不是写出最酷的代码,而是交付有价值的产品——顺便,让自己的简历多一个拿得出手的项目。

(完)

P.S. 如果你也在深圳远程办公,欢迎加我微信交流 Rust 学习心得(最近被 borrow checker 教做人)。或者,单纯想找人吐槽产品经理也行——反正我这边,夜还很长。

评论 0

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