Vue 的 Scoped 样式,本质上是在组件编译阶段,给每个元素打上一个类似“身份证”的唯一属性(比如 data-v-f3f3eg9),然后把 CSS 选择器悄悄改写,变成 .className[data-v-f3f3eg9] 的形式。这样一来,样式就老老实实地作用在当前组件内,不会到处“惹事”。
但也别把它想得太完美,全局选择器该穿透还是穿透,碰上动态内容(比如 v-html 渲染的片段)或者第三方组件插槽里的内容,还是得靠 :deep() 这类穿透手段额外处理。

先说说单文件组件的骨架:、 和 三块缺一不可。样式默认是全局的,只有加上 scoped 属性,才算是给样式装上了“边界线”。
基本结构:三个标签各司其职
每个 .vue 文件,核心就是由这三个部分组成的。它们分工明确,各管一摊:
:负责组件的骨架,必须且仅有一个根元素(比如或):导出组件配置(Vue 2)或使用(Vue 3),数据、方法、生命周期都归它管:负责外观,默认全局生效;加了scoped后,样式就只认当前组件里的 DOM 了
样式作用域:scoped 到底怎么“圈地”的?
加上 scoped 属性后,Vue 在编译阶段就给组件内的每个元素自动加上了一个唯一属性,比如 data-v-f3f3eg9。同时,CSS 选择器也会被重写成带这个属性的形式,从而达到“各扫门前雪”的效果。
- 普通选择器(比如
.btn)会自动转成.btn[data-v-f3f3eg9],确保只命中当前组件内的元素 - 子组件的根元素也会带上这个属性,但子组件内部的其他元素不会——所以父组件的 scoped 样式,没法直接“翻跟斗”到子组件里面去
- 真想穿透怎么办?Vue 3 里用
:deep(.child-class),Vue 2 里用>>>或者/deep/,可以强行突破这个限制
常见误区与应对策略
Scoped 不等于“绝对隔离”。实际开发中,总有几类边缘情况需要注意:
- 动态生成的内容:比如
v-html插入的 HTML 片段,不会被自动加上 scope 属性。这种情况要么手动处理,要么换用 CSS Modules 更合适 - 第三方 UI 组件(如 Element Plus):组件的插槽内容属于子组件,父组件的 scoped 样式默认管不到它们。得用
:deep()或::v-deep才能“渗透”进去 - 全局与局部并存:同一个文件里,可以用一个全局的
块写通用规则,再单独用写局部样式,两不耽误
推荐实践:可维护性与灵活性的平衡
大多数场景下,默认开启 scoped 是最稳妥的做法,能避免样式“污染”到其他地方。对于那些需要跨组件复用的通用样式(比如工具类、主题色变量),可以抽到独立的 variables.css 文件里,或者用 CSS 自定义属性来管理。
- 组件级样式:优先用
scoped,锁死边界 - 跨组件共享:用普通
+ CSS 变量或预处理器@import,保证一致性 - 需要穿透时:明确使用
:deep()并顺手加个注释说明原因,避免滥用导致样式混乱
