找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2318

积分

0

好友

325

主题
发表于 15 小时前 | 查看: 3| 回复: 0

CSS-in-JS引发的疑问:为何选择它?

CSS-in-JS 库承诺带来简洁易用的体验,却可能为开发者埋下诸多隐患:难以理解的类名、令人抓狂的 hydration bug、以及意料之外的性能瓶颈。其最初的使命是将开发者从全局命名空间的噩梦和混乱的样式表中解放出来,但现实是,它往往只是为开发者披上了一层崭新的混乱外衣。

性能更差、可读性更低、消耗更多 CPU 周期——这些是许多开发者面临的困境。而回顾二十年前,一个简单的样式表就能轻松完成的工作,如今却变得如此复杂。从这个角度看,CSS-in-JS 很难被称作是进化,更像是伪装成进步的过度设计。

本文将探讨为何越来越多的开发者选择放弃 CSS-in-JS,转而拥抱原生 CSS 解决方案,以构建速度更快、更易于维护的 Web 应用。

从解放天性到性能瓶颈

CSS-in-JS 诞生之初,感觉就像一场革命。它带来了前所未有的便利:不再有全局样式泄漏,无需担心样式优先级之争,也告别了晦涩难懂的类冲突。开发者可以将样式与组件紧密耦合,一切都变得模块化且“自包含”。

// ComponentA.js
const Title = styled.h1`
 color: red;
 `
// ComponentB.js
const Title = styled.h1`
 color: blue;
 `
// 自动局部作用域以避免样式冲突,两者互不影响!
import styled from "styled-components";
const Button = styled.button`
  padding: 8px;
  background: ${(props) => (props.variant === "primary" ? "blue" : "gray")};
`;
export default function App() {
  return <Button variant="primary">Click</Button>;
}
// 1. 无需维护 class 名、样式直接读取 props
// 2. 按组件加载样式,即组件未渲染 → 样式不加载
// 3. 代码分割(Code Splitting)时,样式自动分割

然而,蜜月期总是短暂的。开发者们逐渐意识到,运行时生成样式并非一个无伤大雅的妥协,而是一颗随时可能引爆的性能定时炸弹。最初旨在简化样式编写的方法,最终可能演变成前端开发中最昂贵的抽象层之一。

通过将 CSS 生成与 JavaScript 执行绑定,我们实际上是将表现层与逻辑层重新耦合在了一起,而这恰恰是当初发明 CSS 时极力想要避免的分离原则。

在客户端动态计算 CSS 的想法听起来巧妙,但当你调试一个因为服务器渲染 (SSR) 过程中服务器和浏览器对类名哈希处理方式不同而导致的加载不匹配问题时,就会体会到其中的痛苦。这种问题与其说是 bug,不如说是轻信过度宣传的代价。CSS-in-JS 库让设置按钮样式这样简单的操作变得像依赖注入一样复杂,更别提仅仅为了把边框改成蓝色,应用包的大小就膨胀了多少。

原本优雅的关注点分离,被包裹在钩子和上下文提供程序中的内联样式混乱所取代。

便利背后隐藏的性能代价

开发者常常以“开销微乎其微”为由为 CSS-in-JS 辩护,但事实往往并非如此。运行时样式会带来可衡量的性能下降,从解析字符串损失的毫秒级时间,到样式重新计算带来的内存开销,任何对性能敏感的商业应用都无法忽视这种风险。

每次组件挂载时,系统都必须创建、注入,有时甚至还要去重样式标签。想象一下,当数百个组件同时进行这个流程时,渲染周期就会变成一场低效的官僚主义混乱。

// Card.js
import {css} from '@emotion/react';
export default function Card({id, color}) {
  // ⚠️ 每个卡片生成唯一样式(运行时创建 CSS)
  const cardStyle = css`
    padding: 12px;
    margin: 8px;
    background: ${color}; /* 动态值 */
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    transition: transform 0.2s;
    &:hover {
      transform: translateY(-2px);
    }
  `;
  return (
    <div css={cardStyle}>
      Card #{id}
    </div>
  );
}
import {useState} from 'react';
import Card from './Card';
export default function App() {
  const [cards] = useState(() =>
    Array.from({length: 500}, (_, i) => ({
      id: i + 1,
      // 随机背景色(确保每个样式唯一)
      color: `hsl(${Math.random() * 360}, 70%, 85%)`
    }))
  );
  return (
    <div>
      <h1>500 个动态卡片 </h1>
      <div style={{display: 'flex', flexWrap: 'wrap'}}>
        {cards.map(card => (
          <Card key={card.id} id={card.id} color={card.color} />
        ))}
      </div>
    </div>
  );
}

例如上面的例子将面临以下挑战:

  • 500 次样式创建<Card> 的 css 模板函数执行 500 次,生成 500 个唯一的 CSS 规则字符串。
  • 500 次 DOM 注入或去重检查 : Emotion 运行时会经过 500 次哈希计算 + 字符串比对检查该样式是否已存在;如果不存在则创建 <style> 标签并注入 500 次 DOM 操作;而且即使样式相似,只要 color 不同就被视为新样式。

为什么要使用 <style> 标签?因为 element.style 无法支持 CSS 的关键特性,例如:选择器、伪类、媒体查询、动画、继承等。

  • DOM 节点爆炸 :浏览器 Elements 面板会出现数百个 <style data-emotion="css">.css-1a2b3c{background:hsl(120,70%,85%);...} 标签。
  • 渲染阻塞:主线程经过 500 次 JS 计算 + DOM 操作阻塞;用户可能看到白屏 1~2 秒(取决于设备性能);Chrome Performance 面板会显示 Long Task(>50ms 的 JS 执行)和大量 Recalculate Style / Layout。

性能测试表明,CSS-in-JS 框架会增加网络和运行时开销。也许在性能强劲的开发机器上感觉不到,但使用低端设备的用户肯定会感受到。曾经是 Web 最大优势的轻量级渲染,如今却被过度抽象所拖累。

最大的矛盾在于:浏览器已经拥有一个久经考验、高度优化的样式处理系统——CSS。而我们却选择用一个更慢、更难调试、有时甚至在页面刷新时出现不一致(尤其是在 SSR 场景下)的 JavaScript 模仿者来替代它。CSS-in-JS 并没有让样式渲染更快,反而常常让调试变得更慢。

然而,这种不良循环仍在继续。框架维护者们不断地打补丁,试图让动态样式“感觉像原生应用”,但仍然无法超越浏览器自身的样式引擎。这就像在轮子里再造一个轮子,只不过这个新轮子可能导致内存泄漏,并且每周都需要通过 npm 更新来修复。

开发者体验 (DX) 的幻象

CSS-in-JS 的拥护者经常声称其能显著改善开发者体验,在项目初期这或许没错,但一旦需要长期维护,情况就会发生变化。乍一看,样式与组件放在一起很现代,也符合人体工程学。作用域类名看起来简洁,变量也易于访问。

但当代码库规模扩大,这种美好的错觉就会逐渐消失。开发者会开始到处寻找缺失的属性插值,疲于应对各种上下文提供程序,甚至为了一个简单的样式覆盖而不得不重写大量逻辑。

// 组件被改得面目全非
const Title = styled.h3`
  color: ${props => props.titleColor || '#333'};
  font-weight: ${props => props.titleWeight || 'normal'};
  font-size: ${props => props.titleSize || '18px'};
  margin-bottom: ${props => props.titleMargin || '8px'};
  // ... 10 个 prop 后
`;

调试 CSS-in-JS 就像玩一场针对类名哈希的扫雷游戏。开发者工具变成了一个充斥着晦涩难懂选择器的荒芜之地。检查一个元素时,你会看到类似 .css-4kq0lj{margin:0 auto} 的代码,非常难以追溯其源头。当十几个样式组件像一棵混乱的家谱树一样互相继承时,想要理清关系几乎是不可能的。

这种对“DX”(开发者体验)的过度宣传,实际上可能掩盖了架构上的根本缺陷。

更糟糕的是,CSS-in-JS 有时会助长过度设计。既然可以导入一个钩子并动态切换主题上下文,为什么还要编写一个简单的媒体查询呢?曾经还在争论是否使用 Flexbox 的开发者们,如今却在争论哪种运行时样式在水合方面能提升 2% 的性能。这无疑加重了开发者的认知负担,而回报却往往微乎其微。

诚然,导入 styled-components 并用反引号编写 CSS 的确能带来短暂的愉悦感。但当应用性能暴跌、构建时间翻倍时,这种快感便会迅速消散。便捷并不等同于优雅或高效,而 CSS-in-JS 的便捷常常是以牺牲代码清晰度和运行时性能为代价的。

回归理性:后 CSS-in-JS 时代

值得庆幸的是,前端社区的风向正在发生转变。即使是最坚定的 CSS-in-JS 拥护者也开始承认,这种方式在大规模、高性能要求的应用中是不可持续的。

像 Remix、Astro 以及 Next.js 等现代框架正在引导开发者回归简洁,即利用传统的 CSS、CSS Modules 或静态提取,而不是运行时生成。核心信息很明确:关注点分离仍然至关重要,而浏览器原生支持的技术往往是最优解。

/* 传统 CSS,但自动局部作用域 */
.card {
  padding: 16px;
  border: 1px solid #ddd;
  border-radius: 8px;
}
.title {
  color: #333;
  font-size: 18px;
}
// Next.js 13(App Router),默认推荐 CSS Modules
import styles from './ProductCard.module.css';
export default function ProductCard({title}) {
  return (
    <div className={styles.card}>
      <h3 className={styles.title}>{title}</h3>
    </div>
  );
}

CSS 变量、容器查询以及作用域样式等现代 CSS 特性的兴起,意味着如今的 CSS 已经可以原生、高效且可预测地处理 CSS-in-JS 试图解决的大部分问题。没有运行时开销,没有哈希冲突,也没有阻塞 DOM 的不可见样式标签,只有即时加载且行为一致的样式。

<!-- CSS 变量(Custom Properties) -->
<html>
<head>
  <style>
    :root {
      --primary-color: #3498db;
      --border-radius: 8px;
    }
    .card {
      background: var(--primary-color);
      border-radius: var(--border-radius);
      padding: 16px;
      color: white;
    }
    /* 深色模式覆盖 */
    .dark-theme {
      --primary-color: #2c3e50;
    }
  </style>
</head>
<body>
  <div class="card"> 默认卡片 </div>
  <div class="dark-theme">
    <div class="card"> 深色卡片 </div>
  </div>
  <script>
    // 用 JS 动态修改
    document.documentElement.style.setProperty('--border-radius', '20px');
  </script>
</body>
</html>
// 容器查询
.container {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  justify-content: flex-start;
  container-type: inline-size;
  // New property to define the name of our container
  container-name: cards-container;
}
//We now specify which container we are listening to
@container cards-container (max-width: 400px) {
  .card-inner {
    flex-direction: column;
  }
  .card-left {
    border-right: none;
    border-bottom: 1px solid #333;
  }
}
// 作用域样式
@scope (.light-theme) {
  :scope {
    background: #cccccc;
  }
  p {
    color: black;
  }
}
@scope (.dark-theme) {
  :scope {
    background: #333333;
  }
  p {
    color: white;
  }
}

CSS 本身没有问题,问题出在我们使用它的方法论上。我们不需要重新发明样式系统,只需要重新理解和尊重那些最初使 Web 稳定运行的边界与原则。答案不在于叠加更多的抽象层,而在于更深入地理解并运用好现有的强大工具。未来不在于将 CSS 嵌入 JavaScript,而在于编写可维护、高性能的样式,使其能够随着 Web 平台的发展而自然演进。

回归基本原理,是前端开发走向成熟的标志,也是向实用主义的正常转变。它意味着我们认识到:通过添加复杂的抽象层来解决由这些抽象自身创造的问题,并非真正的创新。

现代 CSS 如何解决 CSS-in-JS 问题

CSS-in-JS 的诞生源于美好的愿望:模块化、可预测性和组件化,但得到的却常常是伪装成进步的复杂性。Web 并不需要另一个运行时样式引擎或晦涩难懂的哈希值来保持现代性,它需要的是开发者的克制和对原生能力的信任。

目前,CSS 正步入一个全新的发展阶段,简洁再次成为精妙之道,全局样式表与作用域规则可以和谐共存。浏览器承担着繁重的计算工作,这本就是它设计之初的职责。现在是时候停止盲目崇拜那些可能拖慢速度的抽象概念,开始信任这个花费数十年时间不断打磨和优化的 Web 平台了。

CSS 变量 管理动态主题,用容器查询实现精细化的响应式布局,再用作用域样式保证封装性,我们完全可以构建出强大且高性能的界面,同时彻底摆脱对 CSS-in-JS 运行时的依赖。

参考资料与版权声明:本文核心观点启发自 Alexander T. Williams 在 thenewstack.io 发表的文章《CSS-in-JS: The Great Betrayal of Frontend Sanity》,并已进行大量重写、注解与内容扩充,以更贴合当前中文前端开发语境。更多关于前端架构的深度讨论,欢迎访问云栈社区进行交流。




上一篇:DeepSeek开源Engram条件记忆模块,为MoE模型引入存算分离新范式
下一篇:回测有效性:量化投资中Sharpe比率的统计幻觉与多重检验校正
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-14 18:39 , Processed in 0.467787 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表