样式冲突

Vue多组件开发时,组件中的CSS样式默认是全局使用的。例如在A.vue这个组件中定义的样式,也会在B.vue这个组件中生效。

而当项目中使用的组件越来越多时,这种全局通过的CSS样式的方式势必会影响整个项目,导致样式冲突。例如A.vueB.vue都用了.demo这个样式,那么在引入和使用组件时,会造成样式的冲突。相同选择器的样式,究竟是哪个组件中的样式生效?

Vue在引入组件.vue时,如果存在相同的样式导致的冲突,后引入的组件会覆盖先引入的组件中相同的样式。例如A.vueB.vue都用了.demo这个样式:

1import A from './components/A.vue'
2import B from './components/B.vue'
3// B 中的 .demo 会覆盖 A 中的 .demo,并全局生效

Scoped

为了解决这种样式冲突的问题,Vue引入了scoped(样式范围)。使用scoped可以让当前组件中的样式,仅对当前组件模板中的元素生效(样式局部生效,防止冲突)。

scoped的用法是,在.vue中,直接作用在<style>标签上:

1<style scoped>
2  /* ... */
3</style>

例如:

 1<template>
 2<div class="demo">
 3  <h2>用户姓名:{{name}}</h2>
 4  <h2>用户年龄:{{age}}</h2>
 5  <h2>用户性别:{{sex}}</h2>
 6</div>
 7</template>
 8
 9<script>
10export default {
11  name: 'SiteUser',
12  data() {
13    return {
14      name: '张三',
15      sex: '男',
16      age: 21,
17    }
18  },
19}
20</script>
21
22<style scoped>
23.demo {
24  background-color: orange;
25}
26</style>
 1<template>
 2<div class="demo">
 3  <h2>网站名称:{{name}}</h2>
 4  <h2>网站地址:{{url}}</h2>
 5</div>
 6</template>
 7
 8<script>
 9export default {
10  name: 'MySite',
11  data() {
12    return {
13      name: 'Linner\'s Blog',
14      url: 'blog.linner.asia',
15    }
16  },
17};
18</script>
19
20<style scoped>
21.demo {
22  background-color: skyblue;
23}
24</style>

使用了scoped之后,SiteUserSiteUser中的.demo样式就不会互相冲突。因为它们的.demo仅作用于当前.vue文件的模板中,不会作用于其它组件,对其它组件造成影响。

注:

一般不会在App.vue中使用scoped样式。因为App.vue管理着整个项目中所有的组件。所以在App.vue中定义的样式一般是要全局生效的样式。如果在App.vue中使用scoped,那么App.vue中的样式只会对App.vue中的非组件元素生效。

也就是说,使用了scoped的组件,其样式一般不会对子组件生效


Scoped 原理

使用了scoped的组件,Vue会在其真实DOM中的所有非组件元素上添加一个随机的、以data-v-为开头的属性(子组件只会在其根元素上添加这个data-v-属性)。然后Vue会对该组件的所有样式都加上一个属性选择器,例如.demo[data-v-xxxxxxxx]。如此一来,使用了scoped的组件,它们的样式不会被作用到其它元素上。

以上例中的两个组件为例,在App.vue使用它们,并且使用scoped

 1<!-- 
 2  App.vue
 3 -->
 4
 5<template>
 6  <div class="demo">
 7    <div><h1>{{msg}}</h1></div>
 8    <hr>
 9    <MySite/>
10    <hr>
11    <SiteUser/>
12  </div>
13</template>
14
15<script>
16  import MySite from './components/MySite.vue'
17  import SiteUser from './components/SiteUser.vue';
18
19  export default {
20    name: 'app',
21    data() {
22      return {
23        msg: 'Hello!'
24      }
25    },
26    components: {
27      SiteUser,
28      MySite,
29    },
30  }
31</script>
32
33<style scoped>
34.demo {
35  color: red;
36}
37</style>
 1<!-- 
 2  SiteUser.vue
 3 -->
 4<template>
 5<div class="demo">
 6  <h2 class="title">用户姓名:{{name}}</h2> <!-- App.vue 中的 .title 不会对其生效 -->
 7  <h2>用户年龄:{{age}}</h2>
 8  <h2>用户性别:{{sex}}</h2>
 9</div>
10</template>

在浏览器中,生成的真实DOM可能是这样的:

 1<div data-v-7bxxxxxx="">
 2  <div data-v-7bxxxxxx="">
 3    <h1 data-v-7bxxxxxx="" class="title">Hello!</h1>
 4  </div>
 5  <hr data-v-7bxxxxxx="">
 6  <div data-v-2fxxxxxx="" data-v-7bxxxxxx="" class="demo">
 7    <h2 data-v-2fxxxxxx="">网站名称:Linner's Blog</h2>
 8    <h2 data-v-2fxxxxxx="">网站地址:blog.linner.asia</h2>
 9  </div>
10  <hr data-v-7bxxxxxx="">
11  <div data-v-5axxxxxx="" data-v-7bxxxxxx="" class="demo">
12    <h2 data-v-5axxxxxx="" class="title">用户姓名:张三</h2>
13    <h2 data-v-5axxxxxx="">用户年龄:21</h2>
14    <h2 data-v-5axxxxxx="">用户性别:男</h2>
15  </div>
16</div>