我踩了三年坑,终于搞懂了CSS-in-JS和传统CSS该怎么选

测试环境炸了
2026-07-06 11:05
阅读 377

写在前面的话:最近B站评论区好多粉丝私信问我"到底该用传统CSS还是CSS-in-JS",我当初刚入行的时候也被这个问题折磨得够呛,今天就把我踩过的坑一次性讲清楚。


一、先搞清楚这两货到底是啥

1.1 传统CSS:老派但经典

传统CSS就是我们最熟悉的写法,把样式单独写在一个.css文件里,然后通过类名、ID等方式应用到HTML元素上。

/* style.css */
.button {
  background-color: #1890ff;
  color: white;
  padding: 8px 16px;
  border-radius: 4px;
  border: none;
  cursor: pointer;
}

.button:hover {
  background-color: #40a9ff;
}
<!-- index.html -->
<button class="button">点击我</button>

我当初学的时候,觉得这种写法简直完美——样式和结构分离,各司其职,多清晰啊!直到我接手了一个有200多个CSS文件的老项目……

1.2 CSS-in-JS:把样式搬进JavaScript

CSS-in-JS的核心思想是:直接在JavaScript代码中写CSS样式,样式和组件绑定在一起。

// Button.jsx
import styled from 'styled-components';

const Button = styled.button`
  background-color: #1890ff;
  color: white;
  padding: 8px 16px;
  border-radius: 4px;
  border: none;
  cursor: pointer;

  &:hover {
    background-color: #40a9ff;
  }
`;

export default function App() {
  return <Button>点击我</Button>;
}

看到区别了吗?样式直接"住"在组件里,不用再去CSS文件里找对应的类名了。


二、环境准备:手把手带你搭起来

2.1 传统CSS方案环境搭建

用Vite创建一个最基础的React项目:

npm create vite@latest css-traditional-demo -- --template react
cd css-traditional-demo
npm install
npm run dev

项目结构如下:

css-traditional-demo/
├── src/
│   ├── App.jsx
│   ├── App.css          # 组件级样式
│   ├── index.css        # 全局样式
│   └── main.jsx
├── index.html
└── package.json

2.2 CSS-in-JS方案环境搭建

同样用Vite创建项目,然后安装styled-components:

npm create vite@latest css-in-js-demo -- --template react
cd css-in-js-demo
npm install
npm install styled-components
npm run dev

如果你用的是TypeScript,还需要安装类型定义:

npm install -D @types/styled-components

三、核心概念对比:一张表讲清楚

对比维度 传统CSS CSS-in-JS(styled-components)
样式位置 独立的.css文件 写在JS/JSX组件内部
类名生成 手动命名,容易冲突 自动生成唯一类名,零冲突
动态样式 需要拼接类名或内联style 直接通过props控制,非常自然
代码拆分 样式和组件分离,需来回切换 样式和组件在一起,所见即所得
死代码清理 手动排查,容易遗漏 删除组件=删除样式,自动清理
学习成本 低,入门简单 中等,需要理解JSX和模板字符串
运行时开销 有一定运行时开销
服务端渲染 天然支持 需要额外配置
浏览器调试 直接看CSS文件 看到的是生成的哈希类名

四、实战对比:做一个卡片组件

光说概念太抽象了,咱们直接上手写一个实际组件,两种方案都实现一遍,你自然就能感受到差异。

4.1 需求描述

做一个用户信息卡片,包含:

  • 头像(圆形)
  • 用户名
  • 个人简介
  • 支持"高亮"和"普通"两种主题

4.2 传统CSS实现

/* Card.css */
.card {
  border: 1px solid #e8e8e8;
  border-radius: 8px;
  padding: 20px;
  max-width: 360px;
  display: flex;
  align-items: flex-start;
  gap: 16px;
  transition: box-shadow 0.3s ease;
}

.card:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.card--highlighted {
  border-color: #1890ff;
  background-color: #f0f7ff;
}

.card__avatar {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}

.card__content {
  flex: 1;
  min-width: 0;
}

.card__name {
  font-size: 18px;
  font-weight: 600;
  margin: 0 0 8px 0;
  color: #333;
}

.card__bio {
  font-size: 14px;
  color: #666;
  margin: 0;
  line-height: 1.6;
}
// Card.jsx
import './Card.css';

export default function Card({ name, bio, avatar, highlighted = false }) {
  const cardClassName = highlighted ? 'card card--highlighted' : 'card';

  return (
    <div className={cardClassName}>
      <img className="card__avatar" src={avatar} alt={name} />
      <div className="card__content">
        <h3 className="card__name">{name}</h3>
        <p className="card__bio">{bio}</p>
      </div>
    </div>
  );
}
// App.jsx
import Card from './Card';

export default function App() {
  return (
    <div style={{ padding: 40, display: 'flex', gap: 24 }}>
      <Card
        name="张三"
        bio="前端开发工程师,热爱开源"
        avatar="https://i.pravatar.cc/64?u=zhangsan"
      />
      <Card
        name="李四"
        bio="全栈工程师,专注于React生态"
        avatar="https://i.pravatar.cc/64?u=lisi"
        highlighted={true}
      />
    </div>
  );
}

4.3 CSS-in-JS实现

// Card.jsx
import styled, { css } from 'styled-components';

const CardWrapper = styled.div`
  border: 1px solid #e8e8e8;
  border-radius: 8px;
  padding: 20px;
  max-width: 360px;
  display: flex;
  align-items: flex-start;
  gap: 16px;
  transition: box-shadow 0.3s ease;

  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  }

  ${props => props.$highlighted && css`
    border-color: #1890ff;
    background-color: #f0f7ff;
  `}
`;

const Avatar = styled.img`
  width: 64px;
  height: 64px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
`;

const Content = styled.div`
  flex: 1;
  min-width: 0;
`;

const Name = styled.h3`
  font-size: 18px;
  font-weight: 600;
  margin: 0 0 8px 0;
  color: #333;
`;

const Bio = styled.p`
  font-size: 14px;
  color: #666;
  margin: 0;
  line-height: 1.6;
`;

export default function Card({ name, bio, avatar, highlighted = false }) {
  return (
    <CardWrapper $highlighted={highlighted}>
      <Avatar src={avatar} alt={name} />
      <Content>
        <Name>{name}</Name>
        <Bio>{bio}</Bio>
      </Content>
    </CardWrapper>
  );
}

4.4 对比分析

看完两段代码,你会发现几个关键差异:

传统CSS的痛点:

  • 动态样式靠拼接类名card card--highlighted,类名多了容易出错
  • 需要同时维护.jsx.css两个文件,来回切换
  • BEM命名规范(card__name)虽然好用,但写起来确实啰嗦

CSS-in-JS的优势:

  • highlighted属性直接通过props传递,逻辑清晰
  • 所有样式和组件在同一个文件里,不用跳来跳去
  • 组件删除时样式自动清理,不会有残留的死代码

五、进阶场景:动态主题切换

实际项目中经常需要支持主题切换,咱们看看两种方案分别怎么处理。

5.1 传统CSS方案:CSS变量 + 类名切换

/* theme.css */
:root {
  --bg-primary: #ffffff;
  --text-primary: #333333;
  --accent-color: #1890ff;
}

:root[data-theme="dark"] {
  --bg-primary: #1a1a2e;
  --text-primary: #e0e0e0;
  --accent-color: #4fc3f7;
}

.app {
  background-color: var(--bg-primary);
  color: var(--text-primary);
  min-height: 100vh;
  padding: 20px;
  transition: background-color 0.3s ease;
}

.button {
  background-color: var(--accent-color);
  color: white;
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
// App.jsx
import { useState } from 'react';
import './theme.css';

export default function App() {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    const newTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    document.documentElement.setAttribute('data-theme', newTheme);
  };

  return (
    <div className="app">
      <button className="button" onClick={toggleTheme}>
        切换到{theme === 'light' ? '暗色' : '亮色'}模式
      </button>
    </div>
  );
}

5.2 CSS-in-JS方案:ThemeProvider

// theme.js
export const lightTheme = {
  bgPrimary: '#ffffff',
  textPrimary: '#333333',
  accentColor: '#1890ff',
};

export const darkTheme = {
  bgPrimary: '#1a1a2e',
  textPrimary: '#e0e0e0',
  accentColor: '#4fc3f7',
};
// App.jsx
import { useState } from 'react';
import styled, { ThemeProvider } from 'styled-components';
import { lightTheme, darkTheme } from './theme';

const AppWrapper = styled.div`
  background-color: ${props => props.theme.bgPrimary};
  color: ${props => props.theme.textPrimary};
  min-height: 100vh;
  padding: 20px;
  transition: background-color 0.3s ease;
`;

const Button = styled.button`
  background-color: ${props => props.theme.accentColor};
  color: white;
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
`;

export default function App() {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  const currentTheme = theme === 'light' ? lightTheme : darkTheme;

  return (
    <ThemeProvider theme={currentTheme}>
      <AppWrapper>
        <Button onClick={toggleTheme}>
          切换到{theme === 'light' ? '暗色' : '亮色'}模式
        </Button>
      </AppWrapper>
    </ThemeProvider>
  );
}

CSS-in-JS的主题方案明显更优雅——ThemeProvider把主题对象注入到整个组件树,子组件通过props.theme直接访问,类型安全且逻辑集中。


六、我踩过的坑(血泪经验)

坑1:styled-components的性能陷阱

// ❌ 错误写法:每次渲染都创建新的styled组件
export default function List({ items }) {
  return items.map(item => {
    const ItemWrapper = styled.div`  // 这行代码每次渲染都会执行!
      padding: 10px;
      border-bottom: 1px solid #eee;
    `;
    return <ItemWrapper key={item.id}>{item.name}</ItemWrapper>;
  });
}

// ✅ 正确写法:把styled组件定义在组件外部
const ItemWrapper = styled.div`
  padding: 10px;
  border-bottom: 1px solid #eee;
`;

export default function List({ items }) {
  return items.map(item => (
    <ItemWrapper key={item.id}>{item.name}</ItemWrapper>
  ));
}

我当初学的时候犯了这个错误,列表一长页面直接卡死。记住:styled.xxx要在组件外部定义,它本质上是在创建一个新的React组件。

坑2:传统CSS的全局污染问题

/* component-a.css */
.title {
  font-size: 24px;
  color: red;
}

/* component-b.css */
.title {
  font-size: 16px;
  color: blue;
}

两个组件都用了.title这个类名,后加载的样式会覆盖先加载的。解决方案有几种:

  • 使用BEM命名规范手动避免冲突
  • 使用CSS Modules(.module.css后缀)
  • 使用CSS-in-JS自动生成唯一类名

坑3:CSS-in-JS的服务端渲染问题

如果你用Next.js + styled-components,必须配置babel插件,否则服务端渲染时样式会闪烁:

// .babelrc 或 next.config.js
{
  "presets": ["next/babel"],
  "plugins": [["styled-components", { "ssr": true }]]
}

坑4:调试CSS-in-JS时的类名困惑

CSS-in-JS生成的类名长这样:sc-bdVaJa fGQjPz,在浏览器DevTools里看到这种类名会一脸懵。

解决方法——开启displayName:

// babel配置
{
  "plugins": [["styled-components", { "displayName": true }]]
}

开启后类名会变成:Button__StyledButton-sc-1a2b3c,调试体验好很多。


七、到底该怎么选?我的建议

项目类型 推荐方案 理由
小型项目/个人项目 传统CSS或CSS Modules 简单直接,没有额外依赖
中大型组件库 CSS-in-JS 主题系统、动态样式、样式隔离都很方便
内容型网站(博客、官网) 传统CSS 性能优先,SEO友好,无运行时开销
后台管理系统 CSS Modules 或 CSS-in-JS 组件多、复用多,需要样式隔离
需要动态主题切换 CSS-in-JS ThemeProvider方案最优雅
性能敏感场景 传统CSS 零运行时开销,首屏加载更快

说句掏心窝的话: 没有绝对的好坏,只有适不适合。我见过用CSS-in-JS做简单官网结果包体积暴增的,也见过用传统CSS做大型组件库结果样式冲突改到崩溃的。


八、新手常见问题FAQ

Q1:我是纯新手,应该先学哪个?

先学传统CSS。把选择器、盒模型、Flexbox、Grid这些基础打牢,再去学CSS-in-JS。否则你连CSS属性都不认识,用CSS-in-JS也只是换了个地方写你不懂的东西。

Q2:CSS-in-JS会不会被淘汰?

目前来看不会。虽然"零运行时"方案(如vanilla-extract、Linaria)在崛起,但CSS-in-JS的核心思想——样式与组件共存——已经被广泛接受。

Q3:能不能两种方案混用?

可以,但不推荐。混用会增加心智负担,团队协作时也容易产生分歧。选定一个方案就坚持用下去。

Q4:CSS Modules算CSS-in-JS吗?

不算。CSS Modules本质上还是传统CSS文件,只是通过构建工具自动生成唯一类名来实现样式隔离。它没有运行时开销,是一个很好的折中方案。

/* Button.module.css */
.button {
  background-color: #1890ff;
  color: white;
  padding: 8px 16px;
  border-radius: 4px;
}
// Button.jsx
import styles from './Button.module.css';

export default function Button({ children }) {
  return <button className={styles.button}>{children}</button>;
}

九、下一步学习建议

按照这个路径来,稳扎稳打:

第1周:传统CSS基础
  ├── 选择器与优先级
  ├── 盒模型与布局(Flexbox/Grid)
  └── 响应式设计(媒体查询)

第2周:CSS工程化
  ├── CSS Modules
  ├── CSS预处理器(Sass/Less)
  └── PostCSS与Autoprefixer

第3周:CSS-in-JS入门
  ├── styled-components基础用法
  ├── 动态样式与props
  └── ThemeProvider主题系统

第4周:实战与对比
  ├── 用两种方案实现同一个项目
  ├── 性能对比测试
  └── 根据场景做技术选型

十、写在最后

我写这篇教程的初衷,就是看到太多新手在"传统CSS还是CSS-in-JS"这个问题上纠结太久。说实话,这个问题没有标准答案,关键是理解每种方案的优缺点,然后根据你的项目需求做选择。

我当初花了大半年时间,在三个不同项目里分别用了传统CSS、CSS Modules和styled-components,才真正理解了它们各自适合什么场景。所以我的建议是——别光看文章,动手写起来,踩过的坑才是你真正的经验。

如果你在学习过程中遇到问题,欢迎来B站找我,评论区见!我们下期教程再见。

评论 0

最热最新
暂无评论
测试环境炸了Lv.1
0
影响力
0
文章
0
粉丝