说说 React 中引入 CSS 的方式有哪几种?区别?

一、是什么

在 React 项目中,样式管理是一个重要的工程化话题。与传统的 HTML 开发不同,React 组件化的架构要求样式方案也能与组件模型良好配合,实现样式的封装、复用和维护。

目前主流的 CSS 引入方式包括:内联样式、CSS 文件导入、CSS Modules、CSS-in-JS 以及原子化 CSS(Tailwind CSS)。每种方案在作用域隔离、开发体验、性能、SSR 支持等维度各有取舍。

二、内联样式(Inline Styles)

React 支持通过 style 属性直接编写样式,属性值是一个 JavaScript 对象,属性名使用 camelCase:

function InlineStyleExample() {
  const containerStyle: React.CSSProperties = {
    display: 'flex',
    flexDirection: 'column',
    gap: 16,
    padding: 24,
    backgroundColor: '#f5f5f5',
    borderRadius: 8,
  };

  const titleStyle: React.CSSProperties = {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#333',
  };

  return (
    <div style={containerStyle}>
      <h1 style={titleStyle}>内联样式示例</h1>
      <p style={{ color: '#666', lineHeight: 1.6 }}>
        使用 JavaScript 对象定义样式
      </p>
    </div>
  );
}

动态样式是内联样式的优势场景:

function ProgressBar({ value }: { value: number }) {
  const clampedValue = Math.min(100, Math.max(0, value));

  return (
    <div style={{ width: '100%', backgroundColor: '#eee', borderRadius: 4, overflow: 'hidden' }}>
      <div
        style={{
          width: `${clampedValue}%`,
          height: 8,
          backgroundColor: clampedValue >= 100 ? '#52c41a' : '#1890ff',
          transition: 'width 0.3s ease',
        }}
      />
    </div>
  );
}

局限性:

  • 不支持伪类(:hover:focus)、伪元素(::before)、媒体查询
  • 不支持动画 @keyframes
  • 无法利用浏览器的样式缓存优化
  • 样式优先级最高,难以被覆盖

三、CSS 文件导入

将样式写在 .css 文件中,通过 import 引入。这是最传统的方式:

/* UserCard.css */
.user-card {
  padding: 16px;
  border: 1px solid #e8e8e8;
  border-radius: 8px;
  transition: box-shadow 0.2s;
}

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

.user-card__name {
  font-size: 18px;
  font-weight: 600;
  margin-bottom: 8px;
}

.user-card__email {
  color: #666;
  font-size: 14px;
}
import './UserCard.css';

function UserCard({ name, email }: { name: string; email: string }) {
  return (
    <div className="user-card">
      <h3 className="user-card__name">{name}</h3>
      <p className="user-card__email">{email}</p>
    </div>
  );
}

问题:

  • 全局作用域,类名可能冲突
  • 需要通过 BEM 等命名规范手动避免冲突
  • 删除组件后样式可能遗留

四、CSS Modules

CSS Modules 通过构建工具自动将类名转换为唯一的哈希值,实现样式的局部作用域:

/* Button.module.css */
.button {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: all 0.2s;
}

.primary {
  background-color: #1890ff;
  color: white;
}

.primary:hover {
  background-color: #40a9ff;
}

.secondary {
  background-color: #f0f0f0;
  color: #333;
}

.secondary:hover {
  background-color: #d9d9d9;
}

.large {
  padding: 12px 24px;
  font-size: 16px;
}

.disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
import styles from './Button.module.css';

interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'normal' | 'large';
  disabled?: boolean;
  children: React.ReactNode;
  onClick?: () => void;
}

function Button({
  variant = 'primary',
  size = 'normal',
  disabled = false,
  children,
  onClick,
}: ButtonProps) {
  const classNames = [
    styles.button,
    styles[variant],
    size === 'large' && styles.large,
    disabled && styles.disabled,
  ]
    .filter(Boolean)
    .join(' ');

  return (
    <button className={classNames} disabled={disabled} onClick={onClick}>
      {children}
    </button>
  );
}

编译后类名类似 Button_primary_x3f2a,天然避免冲突。配合 clsxclassnames 库可简化类名拼接:

import clsx from 'clsx';
import styles from './Button.module.css';

function Button({ variant = 'primary', size = 'normal', disabled = false, children }: ButtonProps) {
  return (
    <button
      className={clsx(
        styles.button,
        styles[variant],
        { [styles.large]: size === 'large', [styles.disabled]: disabled }
      )}
      disabled={disabled}
    >
      {children}
    </button>
  );
}

五、CSS-in-JS

CSS-in-JS 将样式逻辑和组件逻辑写在同一个文件中,通过 JavaScript 动态生成样式。

styled-components

import styled from 'styled-components';

interface CardProps {
  $elevated?: boolean;
}

const Card = styled.div<CardProps>`
  padding: 24px;
  border-radius: 8px;
  background: white;
  border: 1px solid #e8e8e8;
  box-shadow: ${props => (props.$elevated ? '0 4px 12px rgba(0,0,0,0.15)' : 'none')};
  transition: box-shadow 0.2s;

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

const Title = styled.h2`
  font-size: 20px;
  color: #333;
  margin-bottom: 12px;
`;

const Description = styled.p`
  color: #666;
  line-height: 1.6;
`;

function StyledCard() {
  return (
    <Card $elevated>
      <Title>styled-components 示例</Title>
      <Description>样式和组件定义在一起,支持动态样式和主题。</Description>
    </Card>
  );
}

Emotion

import { css } from '@emotion/react';
import styled from '@emotion/styled';

const dynamicStyle = (color: string) => css`
  padding: 16px;
  border-radius: 8px;
  background-color: ${color};
  color: white;
  font-weight: bold;
`;

function EmotionExample({ color }: { color: string }) {
  return <div css={dynamicStyle(color)}>Emotion 动态样式</div>;
}

const Badge = styled.span<{ $type: 'success' | 'error' | 'warning' }>`
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 12px;
  color: white;
  background-color: ${({ $type }) => {
    const colors = { success: '#52c41a', error: '#ff4d4f', warning: '#faad14' };
    return colors[$type];
  }};
`;

零运行时方案

传统 CSS-in-JS 存在运行时性能开销。新一代零运行时方案在编译阶段将样式提取为静态 CSS:

// vanilla-extract 示例
// styles.css.ts
import { style } from '@vanilla-extract/css';

export const container = style({
  padding: 24,
  borderRadius: 8,
  backgroundColor: '#fff',
  ':hover': {
    boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
  },
});

// Component.tsx
import { container } from './styles.css';

function MyComponent() {
  return <div className={container}>零运行时 CSS-in-JS</div>;
}

六、Tailwind CSS(原子化 CSS)

Tailwind CSS 采用原子化的工具类方式编写样式,每个类只做一件事:

function TailwindCard({ title, description }: { title: string; description: string }) {
  return (
    <div className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md">
      <h2 className="mb-3 text-xl font-semibold text-gray-800">{title}</h2>
      <p className="leading-relaxed text-gray-600">{description}</p>
    </div>
  );
}

动态样式处理:

function Alert({
  type,
  message,
}: {
  type: 'success' | 'error' | 'warning' | 'info';
  message: string;
}) {
  const typeStyles = {
    success: 'bg-green-50 border-green-500 text-green-800',
    error: 'bg-red-50 border-red-500 text-red-800',
    warning: 'bg-yellow-50 border-yellow-500 text-yellow-800',
    info: 'bg-blue-50 border-blue-500 text-blue-800',
  };

  return (
    <div className={`rounded-md border-l-4 p-4 ${typeStyles[type]}`}>
      {message}
    </div>
  );
}

配合 tailwind-mergeclsx 处理样式覆盖:

import { twMerge } from 'tailwind-merge';
import clsx from 'clsx';

function cn(...inputs: (string | boolean | undefined | null)[]) {
  return twMerge(clsx(inputs));
}

function Button({ className, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
  return (
    <button
      className={cn(
        'rounded-md bg-blue-500 px-4 py-2 text-white hover:bg-blue-600',
        className
      )}
      {...props}
    />
  );
}

七、对比总结

特性内联样式CSS 文件CSS ModulesCSS-in-JSTailwind CSS
作用域隔离✅ 天然隔离❌ 全局✅ 局部✅ 局部✅ 工具类
伪类/伪元素
媒体查询
动态样式✅ 容易❌ 困难⚠️ 需配合✅ 容易⚠️ 条件类名
SSR 支持⚠️ 需配置
运行时开销有(传统)
TypeScript 支持✅ 原生⚠️ 需配置⚠️ 插件
包体积无额外取决于内容取决于内容有库体积按需生成
学习成本

八、最佳实践建议

  1. 小型项目 / 组件库:CSS Modules 或 Tailwind CSS
  2. 大型应用:Tailwind CSS + CSS Modules(混合使用)
  3. 需要高度动态样式:CSS-in-JS(推荐零运行时方案如 vanilla-extract)
  4. 设计系统:styled-components / Emotion + 主题系统
  5. SSR 项目(Next.js):Tailwind CSS 或 CSS Modules(避免传统 CSS-in-JS 的 SSR 复杂性)

在实际项目中,可以组合使用多种方案:

// 主体样式使用 Tailwind
// 高度动态的部分使用内联样式
// 复杂动画使用 CSS Modules

import styles from './Chart.module.css';

function DashboardCard({ color, data }: { color: string; data: number[] }) {
  return (
    <div className="rounded-xl bg-white p-6 shadow-sm">
      <h3 className="mb-4 text-lg font-semibold">数据概览</h3>
      <div
        className={styles.chartContainer}
        style={{ borderColor: color }}
      >
        {/* 图表内容 */}
      </div>
    </div>
  );
}

九、总结

React 中的 CSS 方案没有银弹,每种方式都是在作用域隔离、开发体验、性能和灵活性之间做取舍。近年来 Tailwind CSS 在社区中快速增长,成为很多新项目的首选。CSS Modules 作为稳定成熟的方案仍然广泛使用。CSS-in-JS 正在向零运行时方向演进。选择时应综合考虑团队熟悉度、项目规模、SSR 需求和性能要求。