「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。
我们在项目目录(learn_component)下的 src 目录下新建 02_组件的css作用域 文件夹,在该文件夹中新建 App.vue 和 HelloWorld.vue 文件:
App.vue:
<template>
<h2>App</h2>
<hello-world></hello-world>
</template>
<script>
import HelloWorld from './HelloWorld.vue'
export default {
components: {
HelloWorld
}
}
</script>
<style scoped>
h2 {
color: #f00;
}
</style>
注意:这里我们在
<template>下添加了两个根元素(在Vue 3中<template>下是支持多个根元素的,但在Vue 2中只允许有一个根元素),而我们使用的Vetur插件默认会使用eslint-plugin-vue对<template>里面的模板内容进行验证,它不允许<template>下有多个根元素,所以会出现如下报错信息:
解决办法:对
Vuter扩展进行配置,不对模板进行验证(下图中取消勾选Vetur > Validation: Template这一项):
HelloWorld.vue:
<template>
<h2>Hello World</h2>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
同时修改 src/main.js 中引入 App.vue 组件的路径:
import { createApp } from 'vue'
import App from './02_组件的css作用域/App.vue'
createApp(App).mount('#app')
然后我们来看下效果:
你会发现,App.vue 和 HelloWorld.vue 两个组件中的 <h2> 元素中的文字都变成了红色。但我们明明只在 App.vue 文件中的 <style> 上添加了 scoped 属性设置的样式。
这里
<style>上添加了scoped(作用域内的)属性,目的是为了防止组件之间(比如我们这里App.vue组件和HelloWorld.vue组件)的样式相互污染。怎么防止样式污染的呢?从上图中可以看到,Vue会在相应的元素上添加data-v-xxxxxxxx属性(xxxxxxxx是一个十六进制的数字),然后在设置样式时,带有这个data-v-xxxxxxxx属性的相关元素才会设置对应的样式。因为整个.vue文件是交给vue-loader处理的,所以上面这个过程会由vue-loader完成。也就是说,我们在
App.vue中的<style>上添加scoped属性,本意是给App.vue组件的样式添加作用域,但现在的结果是并没有生效(App.vue组件中使用的HelloWorld.vue组件中的样式被App.vue中的样式覆盖了,即父组件中的样式在子组件中也生效了)。从上图中可以看到,作用域没有生效的原因是
Vue在父组件和子组件的根元素上会添加相同的data-v-xxxxxxxx属性,而我们这里恰巧是用标签选择器h2设置的样式,而父子组件中的根元素又都是<h2>,所以相应的样式对子组件中的元素也生效了。因此,结果看起来就有了样式穿透的效果,但个人觉得这种穿透肯定不是Vue的本意,因为Vue的本意应该是:在App.vue中设置的样式,是不应该在HelloWorld.vue中生效的。所以个人觉得这应该是一个bug。当然,在
Vue 2中,一般不会出现上面这种情况。因为在Vue 2中,我们在编写<template>中的内容时,一般会先用一个<div>元素作为根元素,然后在这个根元素下再编写内容:<template> <div> <h2>Hello World</h2> <h2>Hello World</h2> <h2>Hello World</h2> </div> </template>
那么,我们可以用一个根元素进行包裹来避免这样的问题,HelloWorld.vue 中的代码修改如下:
<template>
<div>
<h2>Hello World</h2>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
效果如下:
所以,推荐下面这种格式编写 <template> 模板:
<template>
<div class="xxx">
<!-- 模板的具体内容 -->
</div>
</template>
此外,开发中一般很少直接使用一个标签选择器去设置样式,我们一般会通过给元素设置 class,再通过类选择器去设置样式:
<template>
<h2 class="title">App</h2>
<hello-world></hello-world>
</template>
<script>
import HelloWorld from './HelloWorld.vue'
export default {
components: {
HelloWorld
}
}
</script>
<style scoped>
/* h2 {
color: #f00;
} */
.title {
color: #f00;
}
</style>
最后,针对目前确实存在的这个问题,个人觉得应该还是在 Vue 3 中的 <template> 中可以有多个根组件了,我们编写的模板写法出现了变化,但 vue-loader 还没有完全考虑到这个问题,所以存在这个问题。但既然 scoped 是用来设置作用域的,那就不应该出现样式穿透,所以个人认为上面出现的问题应该是一个 bug。