为什么我说CSS-in-JS是前端“最佳”的糟粕设计?

0 阅读3分钟

如果你是一名前端开发者,特别是React开发者,你一定听说过或使用过CSS-in-JS方案。从Styled-components到Emotion,这些库在短短几年内迅速流行,被无数项目采用。

但今天,我要冒着被喷的风险说一句:CSS-in-JS是个糟糕的设计,它解决了不存在的问题,却创造了真实的新问题。


一、CSS-in-JS的“美好”承诺

支持者们会告诉你CSS-in-JS有多棒:

  • 组件化:样式与组件绑定,不再担心样式污染
  • 动态样式:基于props的动态样式轻而易举
  • 自动处理前缀:不再需要手动写-webkit-
  • 代码简洁:不再需要在不同文件间跳转

听起来很美好,不是吗?但这些“好处”背后,隐藏着巨大的代价。


二、现实中的七宗罪

运行时开销:性能的隐形杀手

CSS-in-JS在运行时解析样式、生成类名、注入到文档中。这意味着用户访问你的网站时,JavaScript必须完成这些额外工作才能显示样式。

对比一下:

  • 传统CSS:浏览器直接解析和应用样式
  • CSS-in-JS:JavaScript执行 → 解析样式 → 生成类名 → 注入样式 → 浏览器应用

在慢速设备或网络条件下,这种差异尤为明显。而这一切,只是为了实现原本浏览器原生就能处理的事情。

开发体验的倒退

“在JavaScript中写CSS”听起来很酷,直到你真正开始使用:

const Button = styled.button`
  background: ${props => props.primary ? 'blue' : 'white'};
  color: ${props => props.primary ? 'white' : 'blue'};
  
  &:hover {
    background: ${props => props.primary ? 'darkblue' : 'lightgray'};
  }
  
  @media (max-width: 768px) {
    font-size: 14px;
    padding: 8px 16px;
  }
`;

这段代码里,你失去了:

  • CSS语法高亮(除非额外安装插件)
  • CSS自动补全
  • CSS linting检查
  • 浏览器DevTools的直接编辑能力

可维护性噩梦

当样式逻辑复杂时,你最终会得到这样的代码:

const ComplexComponent = styled.div`
  ${({ theme, variant, size, disabled }) => {
    // 一大堆JavaScript逻辑
    let styles = '';
    if (variant === 'primary') {
      styles += `background: ${theme.colors.primary};`;
    }
    if (size === 'large') {
      styles += `padding: 20px; font-size: 18px;`;
    }
    if (disabled) {
      styles += `opacity: 0.5; cursor: not-allowed;`;
    }
    return styles;
  }}
`;

这不再是“在JS中写CSS”,而是“用JS逻辑生成CSS字符串”。可读性和可维护性急剧下降。

学习成本陡增

新开发者需要学习:

  1. CSS本身
  2. JavaScript
  3. React
  4. 特定CSS-in-JS库的语法和API
  5. 如何调试这个独特的系统

而他们学到的大多数知识,在离开这个特定技术栈后毫无用处。

SSR和静态生成的复杂性

服务器端渲染变得复杂:

  • 需要收集使用的样式
  • 需要在HTML中注入样式
  • 需要处理hydration不匹配
  • 增加了包大小和内存使用

而这一切对于纯CSS来说,都是不存在的。

调试困难

在浏览器DevTools中,你会看到这样的类名:.sc-1a2b3c4d。想根据类名找到对应的组件?祝你好运。

想了解某个样式来自哪个组件?你需要:

  1. 打开DevTools
  2. 找到元素
  3. 查看混乱的类名
  4. 在代码中搜索这个生成的类名
  5. 或者安装专门的浏览器扩展

三、更好的替代方案

CSS-in-JS试图解决的问题,其实有更优雅的解决方案:

方案一:CSS Modules(真正的组件化CSS)

/* Button.module.css */
.button {
  background: blue;
  color: white;
}

.primary {
  background: darkblue;
}

.button:hover {
  background: lightblue;
}
import styles from './Button.module.css';

function Button({ primary }) {
  return (
    <button className={`${styles.button} ${primary ? styles.primary : ''}`}>
      Click me
    </button>
  );
}

优点

  • 真正的局部作用域
  • 零运行时开销
  • 保持CSS原生能力
  • 易于调试

方案二:Utility-First CSS(如Tailwind)

function Button({ primary }) {
  return (
    <button className={`
      px-4 py-2 rounded
      ${primary 
        ? 'bg-blue-600 text-white hover:bg-blue-700' 
        : 'bg-gray-200 text-gray-800 hover:bg-gray-300'
      }
    `}>
      Click me
    </button>
  );
}

优点

  • 极小的CSS输出
  • 高度一致的设计系统
  • 极少的上下文切换
  • 优秀的性能特性

方案三:纯CSS + 现代特性

现代CSS已经解决了大多数“CSS难题”:

/* 使用CSS自定义属性实现主题 */
:root {
  --primary-color: blue;
  --spacing-unit: 8px;
}

.button {
  background: var(--primary-color);
  padding: calc(var(--spacing-unit) * 2);
}

/* 容器查询 - 即将成为标准 */
@container (max-width: 400px) {
  .button {
    font-size: 14px;
  }
}

四、历史的教训

我们见过这种模式:

  1. 过度抽象:为了解决“复杂”的CSS,我们创建了更复杂的系统
  2. 技术债积累:短期便利,长期维护噩梦

CSS-in-JS可能最终会像其他过度抽象的技术一样,在热情消退后,留下技术债务和后悔的开发者。


结语

有时候,最简单的解决方案就是最好的解决方案。CSS已经存在了25年,浏览器厂商投入了无数资源优化它。也许,我们应该相信这些专家,而不是试图在JavaScript中重新发明轮子。

前端开发的进步,不应该以牺牲Web的根本原则为代价。

简洁、可维护、高性能的代码,才是对我们用户和同事的真正尊重。


互动话题:你在项目中使用过CSS-in-JS吗?遇到了哪些问题?欢迎在评论区分享你的经验!

关注我,获取更多前端技术文章