scoped 通过编译时为组件内普通 DOM 元素添加唯一 data-v-xxx 属性,并重写 CSS 选择器强制匹配该属性,实现样式局部作用域;但不作用于 slot 内容、v-html 插入内容、子组件内部元素及动态类名。

scoped 是怎么给 HTML 元素“打标签”的
Vue 编译器在构建阶段,会为
所属组件的 ** 每个普通 DOM 元素 **(非 slot 插槽内容、非子组件内部节点)自动添加一个唯一属性,形如 data-v-469af010。这个 ID 是基于组件文件路径或内容哈希生成的,确保全项目不重复。
注意:只加在当前组件模板直接写的元素上,不会递归加到它引用的子组件内部元素里——子组件自己有自己独立的 data-v-xxxx。
- 根元素一定会被加上该属性
-
内容里的元素默认不加(除非用::v-slotted显式处理) - 动态插入的元素(比如
v-html里的内容)也不会自动带这个属性
scoped 是怎么改写 CSS 选择器的
编译时,Vue 的样式 处理器(通常是 vue-loader + postcss)会把所有原始选择器重写,强制带上对应 data-v-xxx 属性条件。例如:
.btn {color: blue;} .list li:hover {background: #eee;}
会被转成:
立即学习 “ 前端免费学习笔记(深入)”;
.btn[data-v-469af010] {color: blue;} .list[data-v-469af010] li:hover {background: #eee;}
这就让 浏览器 只匹配同时满足「有该类名」且「有该 data 属性」的元素,天然隔离了其他组件的同名类。
- 复合选择器(如
.a .b)中,只有最左的祖先选择器会被加属性;中间和末尾的选择器保持原样 - 伪类、伪元素(
:hover、::before)不受影响,仍作用于目标元素本身 - 权重变高了:
.a[data-v-x]比.a多一个 属性选择器,优先级更高
为什么父组件改不了子组件内部样式
因为子组件内部的
这类元素,既没有继承父组件的 data-v-xxx,也不在父组件模板里直出——它属于子组件自己的 DOM 树,只带子组件自己的 data-v-yyy。
所以父组件写 .desc {font-size: 12px;}(即使加了 scoped),编译后变成 .desc[data-v-xxx] {……},但子组件的
上根本没有 data-v-xxx,自然不生效。
- 想穿透修改?必须用深度选择器:
::v-deep(.desc)(Vue 3 推荐)或旧写法/deep/ .desc -
::v-deep不是伪类,而是编译指令,它会让后面的选择器“跳过”当前组件的 data 属性限制 - 错误写法:
.wrapper ::v-deep .desc—— 这会导致编译结果是.wrapper[data-v-x] .desc,依然无效;正确写法是::v-deep(.wrapper .desc)或更稳妥的::v-deep .desc
scoped 的边界在哪:哪些地方它管不了
scoped 不是 Shadow DOM,它只是编译期的“模拟封装”,因此有明确能力边界:
- 全局样式(如
body、html、第三方库如 Bootstrap 的基础重置)仍会生效 - 通过
v-html注入的 HTML 不会被添加data-v-xxx,其内联样式或 class 无法被 scoped 控制 - 动态类名(
:class="tuc-78a64be6-da08d4-0 {active: isOn} tuc-78a64be6-da08d4-0")如果 class 值是变量,编译器无法预知,也就无法为它加属性选择器 - 使用
@import引入的外部 CSS 文件,若未被 vue-loader 处理(比如没配 postcss 插件),scoped 不起作用
真正要彻底隔离样式,得靠 CSS Modules 或 Web Components;scoped 是轻量折中方案,够用但别指望它万能。






























