vue 样式隔离
一句话就是,vue的样式都被改造过,只能通过
.a[data-v-x]的格式匹配样式。只要不是全局样式,都会被改造成这样。
一个组件使用了<style scoped>样式时,这个组件的直接子元素会被样式隔离。所有当前组件的直接子节点在渲染时会被加上data-v-x的自定义属性,样式会被改成.a[data-v-x]的格式。这样就能避免当前的.a样式影响其他组件的样式。
<template>
<div class="container">
<h1 class="title">这是父组件</h1>
<checkout-bar />
</div>
</template>
<style scoped>
.container .title {
color: red;
}
</style>比如上面的代码,运行后打开控制台检查代码,可以看到<div>、<h1>元素和<checkout-bar>组件的根节点都被设置了相同的data-v-x属性(如果<checkout-bar>也设置了<style scoped>,这时候他就有两个data-v属性,一个来自父组件,一个来自他自身)。.container .title样式变成了.container .title[data-v-x]。样式在匹配的时候必须满足.title的类名还有data-v-x的自定义属性,同时满足两个条件,样式才能生效。每个组件的data-v属性值都是不同的,这样就能避免样式污染了。
修改组件内部样式
想在当前组件的<style scoped>修改某个子组件的内部样式,会因为样式隔离而失败。
<!-- 父组件 -->
<template>
<div class="container">
<h1 class="title">这是父组件</h1>
<checkout-bar class="checkout-bar" />
</div>
</template>
<style scoped>
/* 这里修改checkout-bar组件的.text样式会失败 */
.text {
color: red;
}
</style><!-- checkout-bar 组件 -->
<template>
<div class="checkout-bar">
<p class="text">子组件</p>
</div>
</template>
<style scoped>
.text {
color: aquamarine;
}
</style>上面的代码尝试在父组件的<style scoped>中直接修改子组件内部的text样式,这样会失败。打开控制台可以看到父组件和子组件的data-v属性不同,父组件的样式编辑后会变成.text[data-v-1],子组件直接上是没有data-v-1的属性的,这个值只有父组件的直接子节点才会有,所以直接这样写是无法匹配的。
这里有个细节,父组件可以直接修改子组件根节点的样式。比如下面这样:
<style scoped>
/* 这里修改checkout-bar成功 */
.checkout-bar {
border: 1px solid red;
}
</style>当前组件的所有直接子节点都会赋值相同的data-v,所以checkout-bar的根节点也会有和父组件相同的data-v属性,如果他也设置了scope,那么他会有两个不同的data-v。最后渲染出来的.checkout-bar[data-v-x]样式会正确的匹配到根节点。
这种情况适用于只有一个根节点的情况,如果有多个根,可能会出现意外的结果。一般第一个节点会被赋值和父组件相同的data-v,很容易出现意外情况
样式穿透
想在父组件中修改子组件的样式,可以用:deep()伪类。
<style scoped>
.container :deep(.text) {
color: red;
}
</style>上面的样式会渲染成.container[data-v-x] .text,deep伪类把原本应该加在.text上的data-v移除了,给父组件加上了data-v-x。这样就能通过父节点匹配到组件内部的子节点。这个样式正常渲染会是.container .text[data-v-x],问题就在data-v-x是父组件的值,用这个值无法匹配到组件内部的.text[data-v-123],他们有各自的data-v值。
样式穿透的时候尽量使用后代选择器的方式,就像上面那样。如果直接用:deep(.text)在没有父节点的时候就无效了。
<!-- 父组件 -->
<template>
<h1 class="title">这是父组件</h1>
<checkout-bar />
</template>
<style scoped>
:deep(.text) {
color: red;
}
</style><!-- checkout-bar组件 -->
<template>
<p>无根节点</p>
<p class="text">子组件</p>
</template>
<style scoped>
.text {
color: aquamarine;
}
</style>上面的代码,子组件和父组件都没有根节点,而且checkout-bar也没有父节点,样式穿透失败。有三种解决办法:
- 父组件设置一个根节点。
- 子组件设置一个根节点。
- 调用的时候外层设置一个父容器。
上面的三种方法都会因为,父组件会在当前组件的所有直接子节点,设置相同的data-v属性,而正确匹配节点。直接使用:deep()最终会渲染成这样[data-v-x] .text。所以没有一个父节点就无法通过data-v选中子组件内部节点。
使用.container :deep(.text)后代选择器来穿透样式,就能强制自己写一个父节点,也就能避免样式失效了。