前言

在Vue.js的组件化开发中,常常会对某个组件的style标签加上scoped属性,如​​<style lang='less' scoped>​​,这样做的目的在于使这个组件的样式不能轻易在其他地方被有意或无意修改,以达到封装的目的。值得注意的是,当我们进行组件嵌套时,常常需要修改子组件的默认样式,由组件封装引发的样式修改失效问题常常令人头痛,这其中涉及到的CSS作用域并不简单。因此,只有我们深入理解了Vue.js组件的CSS作用域问题,才能灵活地控制组件的样式以达到预期效果。本文正是基于此,对Vue.js组件的CSS作用域问题进行探讨。

为使文章更加易于理解,本文使用了案例进行展示,这些案例代码量小,结构简单,相当易读。同时为使结论更加具有一般意义,本文往往会在案例后进行总结,以便读者可以应用其中的规律限于篇幅,部分结论没有展示效果,不过都经笔者测试可行,读者可自行测试,这并不复杂。

一、简单结构搭建

这里将要阐述一个简单的示例,父组件为​​App​​,子组件为​​Child​​。

​子组件Child​​的定义文件​​Child.vue​​中代码如下:

<template>
<div id="child">
<h2>子组件h2</h2>
<h3>子组件h3</h3>
</div>
</template>

<script>
export default {
name: 'Child'
}
</script>

<style scoped>
h3 {
background-color: blue;
}
</style>
<style>
h2 {
background-color: blue;
}
</style>


可以看到,​​子组件Child​​有一个根元素​​div#child​​,根元素下有两个子元素​​h2​​和​​h3​​。在样式中设置​​h2​​和​​h3​​背景颜色也为蓝色,为便于比较,​​h3​​使用了​​scoped​​,​​h2​​则未使用​​scoped​​。

现在来看​​父组件App​​,它将嵌套​​子组件Child​​,代码如下:

<template>
<div id="app">
<Child/>
<h2>父组件h2</h2>
<h3>父组件h3</h3>
</div>
</template>

<script>
import Child from './components/Child'

export default {
name: 'App',
components: {
Child
}
}
</script>

<style scoped>
h3 {
background-color: red;
}
</style>
<style>
h2 {
background-color: red;
}
</style>



​父组件App​​有一个根元素​​div#app​​,根元素中首先嵌套了​​子组件Child​​,然后是​​自有的h3和h2元素​​。在样式中设置​​h2​​和​​h3​​背景颜色为红色,同样,​​h3​​使用了​​scoped​​,​​h2​​则未使用​​scoped​​。

二、执果索因:先看效果

以上结构搭建完成后,我们很可能相当好奇:

两个​​h3元素​​和​​h2​​元素分别显示何种背景颜色呢?他们的背景又是如何生效的呢?

我们先来看浏览器显示的效果:

Vue.js中scoped引发的CSS作用域探讨_后缀

这个结果可能令我们相当意外。子组件显示了子组件自身定义的样式,然而父组件的​​h2​​显示了子组件中对​​h2​​设置的样式,父组件的​​h3​​显示了父组件自身对​​h3​​的样式设置。这可能相当令人困惑,事实上,这正是我们要探讨的​​是否加scoped引发的样式的作用域问题。​

三、html结构规律:​​data-v-x​

我们先在浏览器中查看实际生成的html代码,如下:

<div data-v-04c2046b id="app">
<div data-v-10e5fd3c data-v-04c2046b id="child">
<h2 data-v-10e5fd3c>子组件h2</h2>
<h3 data-v-10e5fd3c>子组件h3</h3>
</div>
<h2 data-v-04c2046b>父组件h2</h2>
<h3 data-v-04c2046b>父组件h3</h3>
</div>


对于结构,我们应该不会有什么疑问,重点是其中​​html元素​​增加的​​data-v-x属性(x是一串数字)​​。

需要说明的是,这里的​​x​​不是特定的,可能随测试环境的不同而不同,上面是笔者测试时生成的情况。

可以发现,父组件App的​​根元素及子元素​​都增加了一个特定的​​[data-v-04c2046b]属性​​,​​子组件Child​​的​​根元素及子元素​​都增加了一个另一个特定的​​[data-v-10e5fd3c]属性​​,并且子组件根元素同时增加​​[data-v-04c2046b]属性​​和[data-v-10e5fd3c]属性`。

那么是否可以总结出结论:组件的根元素、子元素、子组件根元素都会增加data-v-x属性呢?

首先,并非所有组件都会增加​​data-v-x属性​​,事实上是否会增加​​data-v-x属性​​是由组件自身是否存在含​​scoped​​的​​style​​标签决定的。

也就是说,如果组件中不存在​​style​​标签或者虽然有style标签但是没有加​​scoped​​,那么组件的元素便不会增加​​data-v-x属性​​。因此,得出第1个结论:

结论1:data-v-x属性的增加是由组件中包含​scoped​​style​标签引发。

第二个问题是,以上示例中,父组件和子组件都最多只有两层html结构,假使html元素嵌套得更深,那么组件的后代元素是否都会增加​​data-v-x属性​​呢,而不仅仅是子元素?

答案是肯定的,即结论2:

结论2:组件的根元素、组件自身的后代元素、子组件的根元素都会加上该组件特定的data-v-x属性。

这里,之所以用了“自身”这个词,原因在于子组件的后代元素虽然也会编译成父组件的后代元素,却不会加上父组件的​​data-v-x​​属性(下同)。

那么,子组件哪些元素会增加子组件特定的​​data-v-x​​属性呢?事实上,由结论2,只要将子组件看作一个没有子组件的父组件,那么不难得出:

结论3:子组件的根元素、子组件的后代元素会加上子组件特定的data-v-x属性。

四、CSS规律

现在,我们来看CSS样式是怎样作用的。

(一)不加scoped的style样式

父组件和子组件都设置了​​h2​​元素的样式,对于父组件和子组件的​​h2​​元素,起作用的都是子组件的样式设置,即:

h2 {
background-color: blue;
}


同时,浏览器中可以看到,父组件中的样式设置被覆盖:

h2 {
background-color: red;
}


:首先,我们发现两者​​h2​​选择器后面都没有加上​​[data-v-x]​​,即结论1:

结论1:无论父子组件,不加scoped的style标签中的选择器不会增加[data-v-x]属性选择器。

同时,由于父组件的样式被覆盖,因此得出第二个结论:

结论2:对于同一个选择器,子组件不含scoped的style标签中的样式优先级高于父组件。

(二)加scoped的style样式

对于父组件的​​h3​​元素,起作用的是

h3[data-v-04c2046b] {
background-color: red;
}


对子组件的​​h3​​元素,生效的是

h3[data-v-10e5fd3c] {
background-color: blue;
}


我们发现,只有组件自身含scoped的style标签中对h3设置的样式生效。

这不难理解,写在含​​scoped​​的​​style​​标签中的选择器都会加上形如​​[data-v-04c2046b]​​这样的属性选择器后缀。虽然在父、子组件含​​scoped​​的​​style​​标签中都对​​h3​​样式进行了设置,但​​h3​​加上的是不同的​​data-v-x​​属性选择器后缀,因此相互不能匹配,彼此毫无影响。

因此,得出以下结论:

结论3:无论父子组件,加scoped的style标签中的选择器都会增加[data-v-x]属性选择器。

结论4:对于同一个选择器,父子组件含scoped的style标签中的样式互不影响。

(三)同一个组件,加或不加scoped

如果我们在子组件含scoped的style中增加:

h2 {
background-color: orange;
}


那么,最终效果如下:

Vue.js中scoped引发的CSS作用域探讨_选择器_02

现在,我们捋一捋为什么会是这个效果:

首先,来看子组件的​​h3​​,父组件含​​scoped​​的​​style​​对​​h3​​的设置不会影响子组件(结论4),那么子组件的​​h3​​将显示蓝色。对于父组件的​​h3​​,同样由结论4,显示红色。对于父组件的h2,由结论2显示蓝色,并且子组件含scoped的style中对h2的设置影响不到父组件。

对于子组件的​​h2​​,由结论2,本应显示蓝色,但是,子组件含scoped的style中对h2的设置将覆盖子组件不含scoped的style中对h2的设置,因此显示黄色。

由上,我们得出新的结论:

结论5:在同一个组件中,对于同一个选择器,含scoped的style中设置的样式优先级高于不含scoped的style中设置的样式。

这不难理解,含scoped的style中的选择器会加上特定的​​data-v-x​​属性选择器后缀,优先级将超过不带后缀的选择器。

五、小结

html篇:

1、某个组件自身的元素是否会加上​​data-v-x​​属性,看该组件是否有带scoped的style标签,而不论这个style标签中有无样式设置,写了什么样式。

2、若某个组件有带scoped的style标签,则该组件的根元素、组件自身的后代元素、子组件的根元素都会增加​​data-v-x​​属性。

3、子组件的根元素会受到父组件​​data-v-x​​属性影响,非根元素则始终不会。

CSS篇:

1、含scoped的样式:父子组件互不影响,原因是增加了属性选择器后缀,彼此不能匹配。

2、不含scoped的样式:父子组件相互影响,但子组件优先级高于父组件。

3、同一个组件:不含scoped和含scoped的样式,含scoped的优先级更高,原因也是含scoped的选择器增加了属性选择器后缀。