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——去掉了对.btn的data-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前端网发表,如需转载,请注明页面地址。
code前端网



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