React × Vue

CSS Modules 与 scoped

CSS Modules 通过生成后的类名隔离样式;Vue scoped 为组件模板和选择器添加作用域属性。

核心结论

两种方案都在构建阶段避免局部类名冲突。CSS Modules 把类名映射显式导入 JavaScript;Vue SFC 的 scoped 同时改写模板属性和 CSS 选择器。

构建流程

组件关系与数据流

ComponentBuildStyle

差异焦点CSS 以模块对象进入 JavaScript,类名引用是显式的。

实线表示依赖、数据或命令传递,虚线表示事件、回调或清理路径。

flowchart TB
  accTitle: React CSS Modules 显式导入流程
  accDescr: 独立 module CSS 文件在构建时生成唯一类名和 JavaScript 映射,ReactDemo 必须 import styles 并通过 className 显式读取映射。
  source[("ReactDemo.module.css")] --> build{{"构建时生成局部类名"}}
  build --> mapping[("styles 对象映射")]
  mapping -->|"import styles"| demo["ReactDemo.tsx"]
  demo -->|"className={styles.card}"| dom[("DOM 唯一类名")]
  class demo component
  class build process
  class source,mapping,dom resource
  classDef state fill:#fffdf8,stroke:#149eca,color:#25221d,stroke-width:1.2px
  classDef component fill:#e4f5fa,stroke:#149eca,color:#25221d,stroke-width:2px,font-weight:600
  classDef process fill:#f4fafb,stroke:#149eca,color:#25221d,stroke-width:1.6px
  classDef resource fill:#eee9df,stroke:#817a70,color:#25221d,stroke-width:1.4px

样式文件与组件文件分离,通过 import 和 styles.* 显式绑定生成类名。

React 流程图
React 组件关系与数据流

滚轮缩放 · 按住拖动 · 双击重置 · Esc 关闭

组件关系与数据流

ComponentBuildStyle

差异焦点作用域由 SFC 编译器注入,无需在脚本中导入样式映射。

实线表示依赖、数据或命令传递,虚线表示事件、回调或清理路径。

flowchart TB
  accTitle: Vue SFC scoped 编译流程
  accDescr: template 和 style scoped 位于同一个 Vue SFC,编译器同时改写模板属性与 CSS 选择器,普通 class 无需通过 JavaScript 导入。
  sfc["VueDemo.vue SFC"] --> build{{"同时编译 template + style"}}
  build --> template[("模板添加 data-v-*")]
  build --> styles[("选择器追加 data-v-*")]
  template --> dom[("匹配当前组件 DOM")]
  styles --> dom
  class sfc component
  class build process
  class template,styles,dom resource
  classDef state fill:#fffdf8,stroke:#2f9d6b,color:#25221d,stroke-width:1.2px
  classDef component fill:#e4f4eb,stroke:#2f9d6b,color:#25221d,stroke-width:2px,font-weight:600
  classDef process fill:#f2f8f4,stroke:#2f9d6b,color:#25221d,stroke-width:1.6px
  classDef resource fill:#eee9df,stroke:#817a70,color:#25221d,stroke-width:1.4px

SFC 编译器同时改写模板和 scoped 选择器,组件内继续使用普通 class。

Vue 3 流程图
Vue 3 组件关系与数据流

滚轮缩放 · 按住拖动 · 双击重置 · Esc 关闭

完整 Demo 源码

以下代码直接读取在线 Demo 使用的源文件,没有省略或改写。

React 源码

import styles from "./ReactDemo.module.css";export function ReactDemo() {  return (    <section className={styles.card}>      <span className={styles.badge}>CSS Modules</span>      <h2>局部作用域卡片</h2>      <p>        JSX 使用 <code>styles.card</code>,构建后生成唯一类名。      </p>      <span className={styles.button}>        仅影响当前组件      </span>    </section>  );}

Vue 3 源码

VueDemo.vue
<template>  <section class="card">    <span class="badge">Vue scoped</span>    <h2>局部作用域卡片</h2>    <p>模板使用普通 class,编译器自动添加作用域属性。</p>    <span class="button">仅影响当前组件</span>  </section></template><style scoped>.card {  display: grid;  gap: 0.75rem;  padding: 1.1rem;  border: 1px solid #83d7b1;  border-left: 3px solid #2f9d6b;  border-radius: 0.25rem;  color: #25221d;  background: #f2f8f4;}.card h2,.card p {  margin: 0;}.badge {  width: max-content;  border-radius: 999px;  padding: 0.2rem 0.55rem;  color: #176947;  font-size: 0.7rem;  font-weight: 800;  background: #c9f5df;}.button {  width: max-content;  border: 0;  border-radius: 0.25rem;  padding: 0.55rem 0.75rem;  color: #fff;  font: inherit;  font-size: 0.78rem;  font-weight: 750;  background: #2f855f;}</style>

在线观察

两个卡片具有相同结构和视觉效果,但绑定生成类名的方式不同。

React

CSS Modules

局部作用域卡片

JSX 使用 styles.card,构建后生成唯一类名。

仅影响当前组件

Vue 3

Vue scoped

局部作用域卡片

模板使用普通 class,编译器自动添加作用域属性。

仅影响当前组件

使用边界

问题CSS ModulesVue scoped
类名如何引用styles.name普通字符串 class
样式位置通常独立 CSS 文件通常与组件同一 SFC
隔离方式构建时重命名类名属性选择器限定范围
深层子组件需显式共享类名/API使用 :deep() 时要谨慎

主题值和运行时数据不要生成大量动态类名,可继续阅读CSS 变量动态样式。