你真的知道怎么用CSS吗?

前言

在 React 项目中管理样式,并非只是"加几个类名"这么简单。每种 CSS 方案背后,都涉及到构建工具如何处理样式模块React 如何调度渲染CSS 如何作用于 Shadow DOM 或虚拟 DOM运行时性能代价等底层机制。

本篇文章将从源码视角、构建流程、性能模型出发,逐一对比目前主流的五种样式方案:传统 CSS、CSS Modules、CSS-in-JS、Tailwind CSS、UnoCSS。

一、传统 CSS:Webpack + 全局作用域的历史遗产

构建与运行机制

在 React 中使用传统 CSS,通常通过:

arduino 复制代码
import './App.css';

这会被 Webpack 的 css-loaderstyle-loader 处理为:

  1. .css 内容转为 JavaScript 模块;
  2. 在模块运行时,通过 DOM API 动态插入 <style> 标签到 <head>
  3. 样式立即生效,但作用于全局作用域。
ini 复制代码
const styleTag = document.createElement('style');
styleTag.innerHTML = require('./App.css');
document.head.appendChild(styleTag);

底层问题

  • 没有作用域隔离,React 虽然使用虚拟 DOM,但 DOM 最终是真实共享的,样式全局污染风险极高;
  • 重渲染不会重新插入样式,除非组件卸载重新挂载;
  • 热更新友好,但可维护性差,组件级开发缺乏样式边界。

二、CSS Modules:静态作用域隔离 + 哈希命名方案

编译原理

CSS Modules 通过在构建阶段对类名进行哈希处理,实现局部作用域。例如:

css 复制代码
/* App.module.css */
.title {
  color: red;
}

最终会被编译为:

css 复制代码
.title__App__a1b2c3 {
  color: red;
}

React 引入时,通过 import styles from './App.module.css',拿到的是:

css 复制代码
{
  title: 'title__App__a1b2c3'
}

也就是说,类名的映射表 在构建阶段已经生成,运行时是纯静态查表行为,无任何样式动态生成逻辑,极大提升了稳定性和性能。

优劣分析

优点:

  • 完全避免全局污染;
  • 保持静态结构,利于代码分割与缓存;
  • 与 TypeScript 类型系统兼容(可生成 .d.ts 声明文件)。

缺点:

  • 无法根据 props 或 state 动态调整样式;
  • className 拼接复杂,缺乏表达力;
  • 不支持嵌套(除非搭配 Sass 等预处理器)。

三、CSS-in-JS:样式即 JavaScript,运行时渲染 + 组件高内聚

原理剖析

以 styled-components 为例,它的核心机制可以简化为:

  1. 使用 Tagged Template Literal 生成样式字符串;
  2. 在运行时将样式插入一个 styleSheet 管理器;
  3. 自动生成唯一类名并绑定到组件 DOM;
  4. 支持 props 驱动样式变化、ThemeContext 等机制。
ini 复制代码
const Button = styled.button`
  background: ${props => props.primary ? 'blue' : 'gray'};
`;

底层通过创建一个缓存结构:

css 复制代码
cache['button__hash'] = `
  .button__hash { background: blue; }
`

并使用 <style> 标签插入到文档流中。

技术特性

  • 动态样式靠运行时计算;
  • 支持服务端渲染(SSR)但需显式注水(hydration);
  • 与 React Context 深度融合,可实现主题切换、响应式样式。

性能代价

  • 初次渲染样式构建成本高(尤其是复杂嵌套组件);
  • 会影响 React Fiber 的生命周期(rendercommit 阶段);
  • 对 SSR 不友好,若未注水完整,可能导致"闪烁"问题。

四、Tailwind CSS:原子类驱动的构建期 CSS 框架

核心逻辑

Tailwind 并不在运行时生成样式,它在构建阶段进行以下流程:

  1. 扫描 HTML/JSX 文件,提取 className 字符串;
  2. 查找匹配的原子类(如 bg-gray-100),生成静态 CSS;
  3. 合并所有原子类定义,生成唯一 CSS 文件;
  4. 不使用的样式被剔除(Purging);

这一机制可通过 Tailwind CLI 或 PostCSS 插件完成。

深层特性

  • 类名即语义,无需样式文件,样式极度可预测;
  • 响应式、伪类、状态变体等内建,类名组合表达力极强;
  • 不支持 props 动态样式,但通过 class 合并实现"伪动态"。
javascript 复制代码
<div className={`text-sm ${isActive ? 'text-blue-600' : 'text-gray-500'}`} />

缺陷与挑战

  • 类名污染视图层,HTML 可读性下降;
  • 类组合拼写出错极难发现,缺少 TS 类型支持;
  • 多团队协作时需统一命名规范或设计 token。

五、UnoCSS:即时生成的原子化样式引擎

底层机制

UnoCSS 是一种"即时模式"的原子 CSS 方案,其核心思想是:

  • 在开发时,拦截类名字符串(通过插件如 Vite 或 webpack loader);
  • 动态构建 CSS AST(抽象语法树)并生成内联样式或单独的 CSS 文件;
  • 仅保留实际使用的类名,对比 Tailwind 更极致"零冗余"。

其运行机制是"虚拟类名映射 + 规则生成器":

css 复制代码
// 输入类名 "text-red-500"
→ 查找规则映射表
→ 生成 CSS: `.text-red-500 { color: #f56565 }`
→ 注入 <style> 或抽离成静态文件

特点

  • 支持变体语法、属性模式、组合规则;
  • 动态生成时几乎无构建冗余;
  • 更适用于小体积、多变组件库开发场景。

总结:构建期 vs 运行时、静态方案 vs 动态方案的取舍

方案 构建期生成 运行时生成 支持动态样式 样式作用域 对 SSR 友好 可维护性
传统 CSS 全局
CSS Modules ⚠️(有限) 局部作用域 良好
CSS-in-JS 局部作用域 ⚠️ 需处理 一般(看规范)
Tailwind CSS ⚠️(类组合) 原子级共享 高(标准化)
UnoCSS ✅(即时) ✅(插件注入) 原子级共享

结语

不同样式方案背后,是对构建工具、运行时性能、组件封装哲学的不同理解和权衡。选择什么样的样式组织方式,归根结底取决于:

  • 项目的复杂度和生命周期;
  • 团队成员的技术背景;
  • 对渲染性能与开发效率的侧重。

理解每种方案的底层机制,才能在项目中做出具有前瞻性的架构决策,而不仅仅是"跟风使用"。

相关推荐
拾光拾趣录5 分钟前
实现 `this` 对象的深拷贝:从“循环引用崩溃”到生产级解决方案
前端·javascript
无羡仙7 分钟前
90%的人都在用的下拉刷新,我把它拆了!
前端·node.js
一念杂记7 分钟前
【实战系列】30分钟开发微信小程序登录&注册&绑定功能
前端·后端·微信小程序
阳焰觅鱼7 分钟前
LRU缓存
前端
福娃B9 分钟前
【CSS】面试必会—浮动布局:让元素“漂浮”的艺术
前端·css·面试
中微子10 分钟前
TypeScript 与 React:现代前端开发的黄金搭档
前端
用户25191624271110 分钟前
Canvas之颜色渐变
前端·javascript·canvas
ZzMemory12 分钟前
详解JavaScript 解构赋值:让你的代码更优雅
前端·javascript·面试
PineappleCoder13 分钟前
CSS那些你不得不懂的“潜规则”(二)
前端·css·面试
前端西瓜哥14 分钟前
图形编辑器开发的一些坑
前端