「这是我参与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
。