Code前端首页关于Code前端联系我们

Vue2里的css deep是啥?怎么用才对?

terry 8小时前 阅读数 7 #Vue
文章标签 Vue2;css deep

不少刚接触Vue2的同学,写样式时总会碰到“子组件样式改不动”“scoped样式冲突”这些麻烦事儿,这时候常听到“用css deep”,可css deep到底是啥?啥场景用?怎么正确写?今天就把这些问题掰碎了讲明白。

css deep是为了解决啥问题?

先得从Vue的scoped样式说起,给组件的<style>scoped后,Vue会偷偷给组件里的每个元素加个类似data-v-xxxx的自定义属性,同时给<style>里的每个选择器也加上这个[data-v-xxxx]后缀,这么做是为了让组件样式“隔离”,只作用于当前组件元素,不会影响其他组件。

但现实中,我们经常需要修改子组件的样式(比如用了Element UI这类第三方组件,或者自己写的通用子组件),可子组件也有自己的scoped(自己的data-v-yyyy),父组件的样式带着自己的data-v-xxxx去选子组件元素,就像“拿自家钥匙开邻居家门”——根本选不到!这时候就得靠css deep来“穿透”scoped的隔离,让父组件样式能影响子组件。

举个例子:
父组件用了子组件<Child />,Child里有个按钮:

<!-- Child.vue -->
<template>
  <button class="btn">点我</button>
</template>
<style scoped>
.btn { background: gray; }
</style>

父组件想把这个按钮改成蓝色,直接写:

<!-- Parent.vue -->
<template>
  <div class="parent">
    <Child />
  </div>
</template>
<style scoped>
.parent .btn { background: blue; } 
</style>

渲染后,父组件的样式会变成.parent .btn[data-v-父组件id],但子组件的按钮是.btn[data-v-子组件id]——两个data-v不一样,样式根本不生效!这时候用deep改造下:

<style scoped>
.parent >>> .btn { background: blue; } 
</style>

编译后,选择器会变成.parent[data-v-父组件id] .btn——去掉了对.btndata-v限制,只保留父组件自己的data-v.parent上,这样就能匹配到子组件的.btn啦~

css deep在Vue2里咋写?

不同CSS预处理器(Sass/Scss、Less)和原生CSS的写法不一样,踩过坑的同学肯定懂,选错语法分分钟编译报错!下面分情况说:

原生CSS(配合vue-loader):用>>>

如果项目没用到Sass/Less,直接写原生CSS,用>>>就能实现deep。

<style scoped>
.parent >>> .child {
  color: red;
}
</style>

但注意!如果用了Sass/Scss/Less这些预处理器,>>>可能会被当成“运算符”报错(比如Sass里没这运算符),这时候得换其他写法~

Sass/Scss中:用/deep/::v-deep

Sass对>>>不友好,所以得用/deep/或者::v-deep,写法长这样:

<style lang="scss" scoped>
.parent {
  // 写法1:/deep/
  /deep/ .child {
    color: red;
  }
  // 写法2:::v-deep
  ::v-deep .child {
    font-size: 14px;
  }
}
</style>

原理是一样的:让Vue-loader编译时,调整选择器结构,穿透scoped的隔离。

Less中:/deep/::v-deep 都能用

Less对这种“穿透语法”兼容性不错,所以写法和Sass类似。

<style lang="less" scoped>
.parent {
  /deep/ .child {
    border: 1px solid blue;
  }
  ::v-deep .child {
    padding: 8px;
  }
</style>

再唠唠原理:

Vue-loader处理scoped样式时,本来会给每个选择器加上[data-v-xxx](比如父组件的选择器变成.parent[data-v-父],子组件的选择器变成.child[data-v-子]),但用了deep后,Vue-loader会“拆分”选择器——把父组件的[data-v-父]只加到父级元素上,子组件的元素不再强制要求带父组件的data-v,这样父组件样式就能选到子组件元素啦~

哪些场景必须用css deep?

不是所有样式问题都得用deep,但碰到这三类场景,不用deep根本搞不定:

自定义第三方组件样式

比如用Element UI、Ant Design Vue这些UI库,组件内部早就加了scoped,想改它们的样式(比如ElButton的 hover 颜色、ElInput的边框),必须用deep穿透。

举个例子改Element UI的ElInput聚焦样式:

<template>
  <div class="my-input">
    <el-input placeholder="请输入" />
  </div>
</template>
<style scoped>
.my-input /deep/ .el-input__inner:focus {
  border-color: #409eff; // 改成想要的颜色
  box-shadow: 0 0 0 1px #409eff inset;
}
</style>

复用组件的差异化样式

自己写了通用子组件(比如<BaseButton>),在不同父组件里需要不同样式,子组件用了scoped,父组件就得用deep去“局部覆盖”。

比如页面A要把BaseButton改成粉色:

<template>
  <div class="page-a">
    <BaseButton />
  </div>
</template>
<style scoped>
.page-a /deep/ .base-btn {
  background: pink;
}
</style>

嵌套组件的深层样式调整

组件嵌套多层(父→子→孙),父组件要改“孙组件”的样式,因为每层都有scoped,必须用deep一步步穿透。

比如父组件改孙组件的字体大小:

<style scoped>
.parent /deep/ .child /deep/ .grandchild {
  font-size: 16px;
}
</style>

用css deep要避开哪些坑?

deep虽好用,但一不小心就踩坑!这几个雷区必须避开:

预处理器的“语法冲突”

前面说过,Sass/Scss里不能直接用>>>,因为Sass会把>>>当成运算符,直接报错!所以写Sass时,必须换成/deep/::v-deep

反例(Sass中会报错):

.parent {
  >>> .child { /* 报错!Sass不认识>>> */ }
}

正例:

.parent {
  /deep/ .child { /* 正确 */ }
  ::v-deep .child { /* 正确 */ }
}

样式污染风险

scoped本来是为了“隔离样式”,但deep穿透后,样式可能不小心影响到其他地方的同名组件。

比如父组件A用.deep .child {},父组件B也用.deep .child {}——如果两个父组件没加“独特前缀”,就会互相影响!

解决办法:给父组件加唯一class,缩小选择器范围。

<template>
  <div class="parent-unique-a">
    <Child />
  </div>
</template>
<style scoped>
.parent-unique-a /deep/ .child {
  color: red;
}
</style>

Vue版本和vue-loader版本的差异

Vue2和Vue3的deep写法有变化(Vue3里::v-deep要单独用,不带空格),但Vue2内部也有细节差异:

  • 老项目(Vue2.5 + vue-loader@13):可能只支持/deep/,不支持::v-deep
  • 新项目(Vue2.6 + vue-loader@15):/deep/::v-deep都支持。

所以要根据项目依赖调整写法,保险起见,统一用::v-deep兼容性更好~

动态样式的“隐藏陷阱”

子组件用:class动态绑定类名(比如<Child :class="{'active': isActive}" />),父组件用deep改样式时要注意:

  • 动态类名(如active不会带scoped的data-v(因为scoped的data-v是静态加到模板元素上的,动态类名是运行时添加的),所以父组件用deep写.active,选择器会变成.parent[data-v-父] .active,能正常匹配子组件的.active

  • 但如果子组件的active静态类名+scoped(比如子组件<style scoped>里的.active),那子组件的.active会带自己的data-v,这时候父组件必须用deep才能穿透。

有没有替代css deep的方案?

deep不是唯一解!这些方法也能解决“跨组件改样式”,各有优劣:

全局样式(慎用,避免污染)

把要改的样式放到没有scoped<style>里,或者专门建个global.css,但要加独特父级选择器,避免影响全局。

比如组件里写两个style标签:

<template>...</template>
<style scoped>
/* 组件内部样式 */
</style>
<style>
/* 全局样式,改子组件 */
.parent-unique .child {
  color: red;
}
</style>

子组件暴露“样式接口”(推荐组件化写法)

子组件通过props接收样式参数,内部用props动态绑定样式,比如子组件<BaseButton>接收color props:

<!-- BaseButton.vue -->
<template>
  <button :style="{ background: btnColor }" class="btn">点我</button>
</template>
<script>
export default {
  props: {
    color: {
      type: String,
      default: 'gray'
    }
  },
  computed: {
    btnColor() {
      return this.color;
    }
  }
}
</script>

父组件用的时候:

<BaseButton color="blue" />

这种方式更符合“组件化思想”,但适合简单样式(比如颜色、尺寸);如果是复杂样式(hover、伪元素),还是得用deep。

利用CSS变量(自定义属性)

子组件里的样式用var(--xxx),父组件在scoped里定义--xxx的值,不用deep也能改。

比如子组件:

<template>
  <div class="child">内容</div>
</template>
<style scoped>
.child {
  color: var(--child-text-color, #333);
  border: 1px solid var(--child-border-color, #ddd);
}
</style>

父组件:

<template>
  <div class="parent">
    <Child />
  </div>
</template>
<style scoped>
.parent {
  --child-text-color: red;
  --child-border-color: blue;
}
</style>

这种方式适合颜色、间距等“可变量”,但复杂选择器(比如hover、多层嵌套)不太好处理,还是得结合deep。

css deep是Vue2里处理scoped样式穿透的核心技巧,理解它的原理、场景、写法,能解决90%的组件间样式冲突,但用的时候要谨慎:避开预处理器语法坑、防止样式污染、结合项目版本选对写法,要是样式问题总卡壳,把deep吃透,再配合全局样式、CSS变量这些方法,组件样式管理能顺畅很多~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门