Vue 深度选择器 :deep 使用说明
一、基本概念
1. 什么是 :deep?
:deep 是 Vue 3 提供的深度选择器 (Deep Selector),用于在 scoped 样式中穿透到子组件的元素。
2. 为什么需要 :deep?
在 Vue 中,<style scoped> 会为每个组件的样式添加唯一的 data-v-* 属性,确保样式只作用于当前组件。但有时候我们需要修改子组件(特别是第三方组件库如 Element Plus)的样式,这时就需要使用 :deep 来穿透样式作用域。
二、工作原理
1. Scoped 样式的作用机制
普通 scoped 样式:
vue
<style lang="scss" scoped>
.my-class {
color: red;
}
</style>
编译后:
css
[data-v-7ba5bd90] .my-class[data-v-7ba5bd90] {
color: red;
}
- 只会匹配当前组件内的
.my-class - 不会匹配子组件中的元素
2. 使用 :deep 穿透样式
vue
<style lang="scss" scoped>
:deep {
.el-form-item__content {
align-items: self-start;
}
}
</style>
编译后:
css
[data-v-7ba5bd90] .el-form-item__content {
align-items: self-start;
}
- 选择器中的
.el-form-item__content不会被添加data-v-*属性 - 可以匹配子组件中的
.el-form-item__content元素
3. 关键区别对比
| 方式 | 编译前 | 编译后 | 能否匹配子组件 |
|---|---|---|---|
| 普通 scoped | .my-class { } |
[data-v-xxx] .my-class[data-v-xxx] { } |
❌ 否 |
使用 :deep |
:deep { .my-class { } } |
[data-v-xxx] .el-form-item__content { } |
✅ 是 |
三、实际应用场景
1. 修改 Element Plus 组件样式
场景: 修改表格搜索表单中表单项的对齐方式
vue
<template>
<zh-table>
<!-- zh-table 内部使用 Element Plus 的 el-form-item -->
</zh-table>
</template>
<style lang="scss" scoped>
:deep {
.el-form-item__content {
align-items: self-start; /* 顶部对齐 */
}
}
</style>
DOM 结构:
html
<!-- index.vue 的根元素 -->
<div data-v-7ba5bd90>
<!-- zh-table 组件 -->
<div data-v-abc123>
<el-form>
<el-form-item>
<div class="el-form-item__content">
<!-- 👈 被匹配到 -->
<input />
</div>
</el-form-item>
</el-form>
</div>
</div>
CSS 匹配过程:
css
/* 编译后的选择器 */
[data-v-7ba5bd90] .el-form-item__content {
align-items: self-start;
}
/* 匹配过程:
1. 找到带有 data-v-7ba5bd90 的元素(index.vue 的根元素)
2. 在该元素的后代中查找 .el-form-item__content
3. 找到子组件中的 .el-form-item__content 元素
4. 应用样式
*/
2. 修改第三方组件库样式
vue
<template>
<el-input />
</template>
<style lang="scss" scoped>
:deep {
.el-input__wrapper {
border-radius: 6px;
background-color: #f5f5f5;
}
.el-input__inner {
height: auto;
color: #000000e0;
}
}
</style>
3. 嵌套使用 :deep
vue
<style lang="scss" scoped>
.zh-default-input {
:deep {
.el-input__wrapper {
border-radius: 6px !important;
}
.el-input__inner {
height: auto;
}
}
}
</style>
编译后:
css
[data-v-xxx] .zh-default-input .el-input__wrapper {
border-radius: 6px !important;
}
[data-v-xxx] .zh-default-input .el-input__inner {
height: auto;
}
四、语法说明
1. 基本语法
scss
:deep(选择器) {
样式规则
}
// 或者使用块语法
:deep {
选择器 {
样式规则
}
}
2. 两种写法对比
写法1:函数式(推荐)
scss
:deep(.el-form-item__content) {
align-items: self-start;
}
写法2:块语法
scss
:deep {
.el-form-item__content {
align-items: self-start;
}
}
两种写法编译结果相同,块语法更清晰易读。
3. 组合使用
scss
:deep {
.el-input__wrapper,
.el-textarea__inner {
border-radius: 6px !important;
}
.el-input.is-disabled .el-input__wrapper {
background-color: #fafafa;
}
}
五、为什么能找到子组件中的元素?
1. CSS 选择器的作用域
:deep 生成的选择器是后代选择器(Descendant Selector):
css
[data-v-7ba5bd90] .el-form-item__content
这个选择器表示:
- 在
data-v-7ba5bd90元素的后代中 - 查找
.el-form-item__content元素 - 不限制层级,可以匹配任意深度的后代
2. DOM 树结构
css
index.vue (data-v-7ba5bd90) ← 样式作用域的根
└── zh-table (data-v-abc123) ← 子组件
└── SearchForm ← 子组件的子组件
└── el-form ← Element Plus 组件
└── el-form-item
└── .el-form-item__content ← 被匹配到
3. 样式穿透机制
:deep让选择器中的目标类不被添加data-v-*属性- 因此可以匹配到子组件中的同名类
- 通过后代选择器在 DOM 树中查找匹配的元素
六、浏览器中的实际效果
1. 实际 DOM 结构
html
<!-- 浏览器渲染后的 DOM -->
<div data-v-7ba5bd90 class="...">
<div data-v-abc123 class="table-search">
<form class="el-form table-search--left">
<div class="el-form-item">
<div class="el-form-item__content">
<!-- 👈 被匹配 -->
<input class="el-input__inner" />
</div>
</div>
</form>
</div>
</div>
2. CSS 规则应用
css
/* 编译后的 CSS */
[data-v-7ba5bd90] .el-form-item__content {
align-items: self-start;
}
匹配过程:
- 浏览器找到所有带有
data-v-7ba5bd90属性的元素 - 在这些元素的后代中查找
.el-form-item__content - 找到匹配的元素并应用样式
七、注意事项
1. 作用域限制
:deep 仍然受到父组件 scoped 的作用域限制:
scss
/* ✅ 正确:可以匹配子组件 */
:deep {
.el-form-item__content {
align-items: self-start;
}
}
/* ❌ 错误:无法匹配子组件 */
.el-form-item__content {
align-items: self-start; /* 只会匹配当前组件 */
}
2. 性能考虑
:deep 选择器会遍历整个 DOM 树,应该:
- ✅ 推荐:使用具体的选择器,减少匹配范围
scss
:deep {
.el-form-item__content { /* 具体类名 */
...
}
}
- ⚠️ 避免:使用过于宽泛的选择器
scss
:deep {
div { /* 太宽泛,可能影响很多元素 */
...
}
}
3. 优先级问题
:deep 生成的样式优先级可能不够高,必要时使用 !important:
scss
:deep {
.el-input__wrapper {
border-radius: 6px !important; /* 确保覆盖默认样式 */
}
}
4. 替代方案
如果只需要修改少量样式,也可以使用:
方案1:不使用 scoped
vue
<style lang="scss">
/* 不使用 scoped,样式会全局生效 */
.el-form-item__content {
align-items: self-start;
}
</style>
方案2:使用全局样式文件
scss
// styles/element.scss
.el-form-item__content {
align-items: self-start;
}
八、常见使用场景
1. 修改 Element Plus 组件样式
vue
<style lang="scss" scoped>
:deep {
.el-button {
border-radius: 6px;
}
.el-input__wrapper {
background-color: #f5f5f5;
}
.el-select__wrapper {
border-radius: 6px;
}
}
</style>
2. 修改自定义组件样式
vue
<template>
<zh-table>
<!-- 需要修改 zh-table 内部的样式 -->
</zh-table>
</template>
<style lang="scss" scoped>
:deep {
.table-search {
padding: 16px;
}
.el-form-item {
margin-bottom: 0;
}
}
</style>
3. 响应式样式穿透
scss
:deep {
.el-form-item__content {
@media (max-width: 768px) {
flex-direction: column;
}
}
}
九、Vue 2 vs Vue 3
Vue 2 语法
scss
/* Vue 2 使用 /deep/ 或 >>> */
/deep/ .el-form-item__content {
align-items: self-start;
}
/* 或者 */
>>> .el-form-item__content {
align-items: self-start;
}
Vue 3 语法
scss
/* Vue 3 使用 :deep() */
:deep(.el-form-item__content) {
align-items: self-start;
}
/* 或者块语法 */
:deep {
.el-form-item__content {
align-items: self-start;
}
}
十、总结
:deep 的核心要点
- 作用:穿透 scoped 样式,修改子组件样式
- 原理 :编译时不为目标选择器添加
data-v-*属性 - 机制:通过后代选择器在 DOM 树中查找匹配元素
- 使用:在需要修改子组件样式时使用
- 注意:合理使用,避免过度穿透影响性能
最佳实践
- ✅ 优先使用具体的选择器
- ✅ 只在必要时使用
:deep - ✅ 注意样式优先级
- ✅ 避免过度穿透影响性能
- ✅ 考虑使用全局样式文件作为替代方案
适用场景
- 修改第三方组件库(如 Element Plus)的样式
- 修改自定义子组件的样式
- 需要保持 scoped 样式隔离的同时修改子组件
:deep 是 Vue 3 中处理样式穿透的标准方式,理解其工作原理有助于更好地使用和调试样式。