跳到主要内容

样式方案的演化

要点

  • 样式方案从「全局命名」走向「模块化/组件化」再到「原子化」,每一步都在降低耦合与认知成本。
  • 原子化 CSS 的价值:更好的可组合性与摇树优化;风险在于约束失效后的 class 泛滥与设计漂移。它不是风口产物,而是长期向「更小粒度、更好约束」演进的结果。
  • 多 runtime(Web/RSC/小程序)下,需要把 tokens 抽象到样式方案之外,避免在构建或运行时动态拼类。

组件库部分已拆为独立页面:组件库的演进

样式方案时间轴

2010:Raw CSS / BEM / OOCSS,语义命名 + 手写层级。
2013:Sass/Less 变量与混入,BEM 体系化。
2015:CSS Modules 作用域隔离。
2017:CSS-in-JS,组件边界 + 运行时成本。
2020:Utility-first,JIT、摇树、tokens 对齐。
2024:Token/原子类跨 Web/RSC/小程序协作。

现场感:几段常见的「升级理由」

  • BEM 项目重构时,遇到 .btn--primary 在四个文件里被覆盖,且 hover 颜色不一致;引入 Utility-first 后把按钮拆成变体,类名归到 cva 工厂,审阅时一眼能看出状态组合。
  • CSS-in-JS 方案在 SSR 下首屏注水 60KB,改为 Tailwind + tokens 后,首屏 CSS 控制在 8KB 以内,且通过 content 精准扫描避免动态膨胀。
  • Mini Program 场景 class 长度有限,原子类 + 预生成模板替换了动态拼接的 style 字符串,稳定性提高,构建速度更快。

各阶段优势 / 劣势 / 适用场景

阶段核心优势主要劣势适用场景常见坑 & 规避
Raw CSS + BEM/OOCSS简单直观;命名带语义全局污染、样式覆盖难排查;复用弱小体量页面、低复杂度站点命名规则不统一 → 制定前缀/分块;避免长选择器链
预处理器(Sass/Less)变量/混入/函数提高复用;BEM 更容易落地仍是全局;易出现嵌套地狱;编译体积膨胀需要一定复用,但没有组件边界要求限制嵌套层级;lint 禁止 #id/深度选择器;保持色板集中
CSS Modules作用域隔离,防止全局污染;类名可组合样式与组件耦合,跨组件复用要额外抽象;难对齐统一 design token可发布组件库、需要隔离的中大型项目建立 shared variables 文件;避免在组件内声明全局变量;主题切换需额外管线
CSS-in-JS组件边界天然隔离;props 驱动样式;SSR 友好(视库而定)运行时开销;编译链复杂;类名可读性差;热更/缓存成本需要强动态样式、主题切换、设计系统与组件强绑定使用零运行时方案(vanilla-extract)或编译模式;监控包体;限定动态样式范围
Utility-first(Tailwind/Uno)类名即样式,低认知切换;JIT/摇树;与 tokens 对齐;生态丰富可读性与约束依赖团队规范;content 不精准会膨胀;动态类易失控需要高迭代速度、设计体系对齐、组件组合化的前后端/多端项目建立 tokens/variants 规范;统一 cn + merge;禁止字符串拼类;content 精准匹配;保留「推荐组合」文档
Token + Headless 组件(shadcn/ui, reka-ui)API 与样式解耦;通过 cva/tailwind-variants 集中管理变体;易被 AI/脚本生成需要自建 design system;无约束时风格漂移需要统一体验的多产品线、希望可插拔主题/品牌的团队维护 tokens 表;评审类名;为 AI 提示加入黑名单/白名单;保留 merge/lint 校验链

迁移建议:如果已有 CSS Modules/组件库想拥抱原子化,可先将公共 tokens 抽出,再在 Headless 组件上叠加 cva/tailwind-variants,逐步替换局部样式。

深入阅读(按阶段拆分)

阶段代表性包速览与跳转

阶段常见用法Demo/图表
Raw CSSnormalize.css, Bootstrap, Bulma全局命名 + 组件 class本页 BEM 例子 + Reset 引入
预处理器Sass, Less, PostCSS, Stylus变量/混入、modifyVars 主题Sass 片段 & PostCSS 插件链
CSS Moduleswebpack/Vite modules, Next.js, vanilla-extract作用域哈希、零运行时模块化 Card 组件
CSS-in-JSstyled-components, Emotion, JSS, vanilla-extract运行时/编译期动态样式主题 Button + 运行时 vs 编译期图
Utility-firstTailwind, Windi, UnoCSS, twin.macroJIT 原子类、attributify、宏模式Tailwind 卡片 + Uno attributify + JIT 时序图
Headless + tokensRadix, Headless UI, shadcn/ui, tailwind-variantstokens → variants → primitivesRadix Tabs、Menu Demo、tokens 流程图

决策树:选择样式方案

原子化 CSS 解决/未解决的问题

  • ✅ 解决:
    • 认知负担:类名即样式,无需跳转文件。
    • 漂移与覆盖:减少全局样式泄漏;content 精准扫描,摇树删除未用类。
    • 设计对齐:tokens 与 variants 让「设计 → 类名」有映射。
  • ⚠️ 风险:
    • 可读性:类名过长、无约束导致审阅困难。
    • 一致性:不同人随意取值,导致色板/间距失控。
    • 体积:动态类或 content 过宽会失去摇树收益。
  • 🚫 不推荐使用的场景:
    • 需要强隔离的可发布组件库(可用 CSS Modules/vanilla-extract)。
    • 极简静态站点,对运行时无需求,模板类名冗长反而成本高。
    • 无法建立 design token/规范的团队,原子类易失控。

与运行时/平台的适配

  • Web/RSC:优先静态生成、content 精准匹配 server 侧模板;避免在服务器计算随机类名。
  • 小程序/多端:注意 class length 限制;预生成静态类,不依赖动态模板拼接。
  • SSR/HMR:Tailwind v4 JIT 足够快;UnoCSS 具更灵活的即时模式,但生态/插件差异需权衡。