生效需同时满足:显式声明 lang="scss"、安装 sass 包、禁用 scoped;$style 仅在 setup() 内可用,类名必哈希化,不支持 :deep()。

直接用 就能实现真正局部化的类名隔离,但很多人卡在“写了没生效”或“$style 访问不到”,核心问题往往不是语法错,而是 loader 缺失、lang 漏写,或误混 scoped。
为什么 不生效
最常见的现象是:类名没被哈希化(比如还是 title 而非 MyComponent_title__abc123),样式依然全局污染。这不是 Vue 版本问题,而是构建链路断了。
- 漏写
lang="scss":只写时,Vue 默认按纯 CSS 处理;用了 SCSS 语法却没声明lang,loader 根本不介入,module也就形同虚设 - 没装
sass包:Vite 和 Vue CLI 都不自带 Sass 编译器,必须显式安装sass(Dart Sass),node-sass已废弃且与 Vue3 不兼容 - 同时写了
scoped和module:两者机制冲突,scoped会被忽略,但开发者可能误以为“双重保险”,结果既没属性选择器也没哈希类名
$style 对象只能在组件实例中访问
$style 是编译后注入的响应式计算属性,不是原始模块对象,也不是全局变量。它只在 setup() 或选项式 API 的 data/computed 中可用,不能在 script 标签顶层、import 语句后或 onMounted 外部直接调用。
- ✅ 正确:
const titleClass = $style.title(在setup()函数内) - ❌ 错误:
console.log($style)写在<script setup></script>最外层(此时$style尚未注入) - ⚠️ 注意:
$style是只读映射,不能动态增删键;也不能在template中直接写{{ $style.title }},必须先解构或赋值给响应式变量
SCSS + CSS Modules 组合使用的实际约束
SCSS 的嵌套、变量、mixin 等特性可以照常使用,但所有最终生成的类名都会被模块化处理——这点和纯 CSS Modules 一致。不过有几处容易被忽略的细节:
- 嵌套选择器中的子类仍受模块化影响:
.container { .title { color: red; } }会生成两个哈希类名,.container和.title各自独立,不构成父子作用域绑定 - 全局变量(如通过
additionalData注入的@import "@/styles/variables.scss")可正常使用,但变量本身不参与模块化,只有最终输出的类名才哈希化 - 不要在
module样式里写:deep()或:global():这些是scoped的语法,CSS Modules 下无效;若需穿透,得靠 JS 动态加类或父组件传 class 名
真正麻烦的点在于:一旦启用 module,你就不能再靠“看类名”调试样式了——所有类名都是运行时生成的哈希串。开发时得习惯用 Vue Devtools 查看 $style 映射,或者打开构建产物里的 .css 文件确认输出是否符合预期。别指望浏览器审查元素里还能认出自己写的 .button。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











