样式冲突

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

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

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

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

Scoped

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

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

<style scoped>
  /* ... */
</style>

例如:

<template>
<div class="demo">
  <h2>用户姓名:{{name}}</h2>
  <h2>用户年龄:{{age}}</h2>
  <h2>用户性别:{{sex}}</h2>
</div>
</template>

<script>
export default {
  name: 'SiteUser',
  data() {
    return {
      name: '张三',
      sex: '男',
      age: 21,
    }
  },
}
</script>

<style scoped>
.demo {
  background-color: orange;
}
</style>
<template>
<div class="demo">
  <h2>网站名称:{{name}}</h2>
  <h2>网站地址:{{url}}</h2>
</div>
</template>

<script>
export default {
  name: 'MySite',
  data() {
    return {
      name: 'Linner\'s Blog',
      url: 'blog.linner.asia',
    }
  },
};
</script>

<style scoped>
.demo {
  background-color: skyblue;
}
</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

<!-- 
  App.vue
 -->

<template>
  <div class="demo">
    <div><h1>{{msg}}</h1></div>
    <hr>
    <MySite/>
    <hr>
    <SiteUser/>
  </div>
</template>

<script>
  import MySite from './components/MySite.vue'
  import SiteUser from './components/SiteUser.vue';

  export default {
    name: 'app',
    data() {
      return {
        msg: 'Hello!'
      }
    },
    components: {
      SiteUser,
      MySite,
    },
  }
</script>

<style scoped>
.demo {
  color: red;
}
</style>
<!-- 
  SiteUser.vue
 -->
<template>
<div class="demo">
  <h2 class="title">用户姓名:{{name}}</h2> <!-- App.vue 中的 .title 不会对其生效 -->
  <h2>用户年龄:{{age}}</h2>
  <h2>用户性别:{{sex}}</h2>
</div>
</template>

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

<div data-v-7bxxxxxx="">
  <div data-v-7bxxxxxx="">
    <h1 data-v-7bxxxxxx="" class="title">Hello!</h1>
  </div>
  <hr data-v-7bxxxxxx="">
  <div data-v-2fxxxxxx="" data-v-7bxxxxxx="" class="demo">
    <h2 data-v-2fxxxxxx="">网站名称:Linner's Blog</h2>
    <h2 data-v-2fxxxxxx="">网站地址:blog.linner.asia</h2>
  </div>
  <hr data-v-7bxxxxxx="">
  <div data-v-5axxxxxx="" data-v-7bxxxxxx="" class="demo">
    <h2 data-v-5axxxxxx="" class="title">用户姓名:张三</h2>
    <h2 data-v-5axxxxxx="">用户年龄:21</h2>
    <h2 data-v-5axxxxxx="">用户性别:男</h2>
  </div>
</div>