css modules比scoped更彻底防污染,因其将.class编译为唯一哈希名(如_button_3kj9s),从根源杜绝命名冲突;而scoped仅靠[data-v-hash]属性选择器,类名仍存在、易被覆盖或透传泄漏。

直接用 <style module></style> 就能启用 CSS Modules,它比 scoped 更彻底地切断类名全局性——不是靠属性选择器“围栏”,而是把 .button 编译成类似 _3zyde4l1yATCOkgn-DBWEL 这样的唯一哈希名,从根源上杜绝冲突。
为什么 <style module></style> 比 scoped 更适合防污染
scoped 本质是加 [data-v-xxxx] 属性选择器,仍依赖类名本身存在;而 CSS Modules 把类名“重写”成不可复用的字符串,即使两个组件都写 .icon,编译后也完全不重合。这对组件库、微前端或多人并行开发尤其关键。
-
scoped下,若子组件透传了父组件 class(比如<slot :class="props.class"></slot>),样式可能意外泄漏 -
module下,所有类名必须显式通过$style.xxx或命名空间引用,不存在“漏掉”的 class -
module支持composes组合其他模块样式,且组合后仍是局部作用域;scoped不支持跨组件样式复用
$style 和命名模块的写法差异
默认不带名字的 <style module></style> 会绑定到 $style 对象;加了名字(如 module="btn")就得用对应变量名访问,不能混用。
- 未命名写法:
<div :class="$style.button">,对应 <pre class="brush:php;toolbar:false;"><style module>.button { ... }</style></pre> <li>命名写法:<code><div :class="btn.button">,需配合 <code><style module="btn"></style> - 含连字符的类名(如
.btn-primary)必须写成$style['btn-primary'],否则 JS 语法报错 - 文件后缀必须是
.vue——.css或.scss单独文件即使带module属性也不会被识别为 CSS Modules - Vite 默认支持
<style module></style>,但若你手动配了css.modules选项,得确认没覆盖默认行为(比如误设generateScopedName导致哈希规则异常) - Vue CLI 项目要检查是否装了
vue-style-loader和css-loader?modules,旧版脚手架可能默认关掉了 CSS Modules - 控制台出现
Cannot find module './xxx.module.css'?说明你用了import styles from './xxx.module.css'写法——这是 React 风格,在 Vue 中只认<style module></style>标签内定义的样式 - 推荐拼接:
:class="[$style.button, isActive ? $style.active : '']" - 想复用全局 reset 或工具类?用
<style module></style>内的:global(.reset) { ... }显式声明,而不是硬塞字符串 - 需要穿透到子组件(比如第三方 UI 库)?CSS Modules 本身不提供深度选择器,得回退用
<style scoped></style>+:deep(),但这就放弃了模块化隔离优势
Vite 或 Vue CLI 下常见失效原因
样式没生效?大概率不是写法错,而是构建链路没接上。
动态类名和第三方 class 混用的坑
别用 classNames($style.button, 'global-class') 这种写法——'global-class' 是纯字符串,不会被 CSS Modules 处理,一旦全局有同名类就冲突;更糟的是,构建时根本不会警告。
真正难处理的不是怎么写,而是团队里有人习惯写 scoped,有人用 module,还有人混着来——最终导致同一项目里三种样式隔离机制并存,维护成本翻倍。选一种,全量推进,比补丁式修复重要得多。











