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前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。