Scoped CSS 仅在构建阶段为元素添加 data-v-xxx 属性并重写选择器,不提供运行时防护,依赖构建工具,CDN 引入或 SSR 缺失插件时失效,:deep() 仅解除目标选择器作用域限制且仅穿透一级。

Scoped CSS 不会“锁定”样式,它只做一件事:在构建阶段给元素加 data-v-xxx 属性,并把你的 .class 改成 .class[data-v-xxx]。它不拦截 JS 动态插入、不处理 v-html、也不管你手写错选择器——这些都得你自己兜底。
scoped 是编译期行为,不是运行时防护
它依赖构建工具(比如 Vite 的 @vitejs/plugin-vue)在解析 SFC 时调用 @vue/compiler-sfc 和 PostCSS 插件完成转换。这意味着:
- 直接通过 CDN 引入
vue.global.js时,scoped完全不生效 - 服务端渲染(SSR)若未启用对应插件或未同步生成
data-v-xxx,样式会丢失匹配 -
v-html渲染的内容、document.createElement创建的节点,都不会自动带data-v-xxx - 子组件根元素会被父组件和自身同时打上两个
data-v-xxx属性(如data-v-parent data-v-child),这是设计行为,不是 bug
:deep() 只解除目标选择器的作用域限制
:deep(.child) 的作用非常具体:它只移除 .child 这个类名的选择器约束,但前面的父级路径仍保留 [data-v-xxx]。例如:
.parent :deep(.child) { color: blue; }
编译后变成:
Vue 3 组合式 API 与 最佳实践,涵盖代码组织、useXxx 模式、组合式函数设计、Store 集成、响应式优化及功能抽取。
立即学习“前端免费学习笔记(深入)”;
.parent[data-v-7ba5bd90] .child { color: blue; }
注意两点:
- 如果
.child所在元素尚未挂载(比如被v-if控制),这条规则存在但不触发 -
:deep(div)或嵌套:deep(:deep(.x))没有意义,第二层:deep()不会被识别 - 不要混用
::v-deep和:deep()—— 后者是 Vue 3 官方推荐,前者仅兼容旧写法
子组件样式穿透失败?先确认作用域边界
父组件的 scoped 样式默认进不去子组件内部,这是隔离机制的正常表现。但有三个常见误判点:
- 以为
:deep()能让父样式影响任意层级 —— 实际只能穿透一级(到子组件根节点下的直系后代),且依赖子组件 DOM 结构稳定 - 在子组件用了
el-form等第三方组件时,没检查其内部是否用了teleport(如弹窗、下拉框),这类节点已脱离当前组件树,:deep()失效 - 用
scoped+css modules时,类名被哈希重命名,直接写原始类名无法匹配,得用$style.xxx或:global()
真正难处理的不是怎么写 :deep(),而是判断该不该穿透——多数时候,该改的是子组件暴露的 prop 或 class 绑定,而不是在父组件里硬撬样式。

















