Vue 中存在 scoped 属性,HTML5中也存在一个 scoped 属性,而且,这两者都是针对 css 样式处理的属性,所以很多文章在 解释 Vue scoped 的时候,都会把两者混为一谈,直接进把 HTML5 scoped 的定义搬到 Vue scoped 上,其实这是不对的,这两个不是一个东西。
HTML5 scoped
在 W3C 和 MDN 文档上是这么说的:
该属性已被移除,不建议使用。
该属性是一个布尔属性。如果使用该属性,则样式仅仅应用到 style 元素的父元素及其子元素。
第一句话就是,不建议使用这玩意。Vue 这么大受众的框架,肯定不会不考虑到这一点。
再就是这玩意只有火狐支持,更不符合项目初衷。
使用方法 和 Vue scoped 一致,直接写在 style 标签内部
<div>
<style type="text/css" scoped>
h1 {
color: red;
}
p {
color: blue;
}
</style>
<h1>这个标题是红色的</h1>
<p>这个段落是蓝色的。</p>
</div>
展示效果
控制台DMO结构
可以看到,在 DMO 结构中 ,只是把我编写的代码原封不动的渲染了,并没有做任何处理,这就是和 Vue scoped 的最大区别
后面再讲到 Vue scoped 原理的时候会做个对比,更容易看出区别在哪。
Vue 组件的样式覆盖
Vue 项目中,每个组件内都会存在一个 <style></style> ,用来编写组件样式,如果组件过多,那么在开发过程中难免会出现,类名相同的情况。一旦类名相同,那么在编译的时候,就可能出现问题。
例如 :组件 Student 存在一个 demo 的class,组件 School 也同时存在 demo 的class ,虽然说这两个 class 代表的样式在各自组件中是不一样的,但是,展示的效果却不是我们想要的。
// Student 组件
<template>
<div>
<p class="demo">Student组件</p>
</div>
</template>
<style>
.demo {
background:orange
}
</style>
// School 组件
<template>
<div>
<p class="demo">School组件</p>
</div>
</template>
<style>
.demo {
background:red
}
</style>
// App 组件使用
<template>
<div id="app">
<School />
<Student />
</div>
</template>
如果按照我们设想的,School 组件展示 红色 ,Student 组件展示橙色,但是实际效果是
两个组件都展示的 橙色,Student 组件的样式覆盖了School 组件的样式。这是因为我们在引入组件的时候,Student 组件是后引入的。如果我们先引入 Student 组件,那展示效果又不一样了。
这是因为在webpack 编译的时候,所有组件内的 style 都会合并,相同类名的样式自然就会放到一起,根据css规则,后面的样式自然会覆盖前面的样式。
我把这个空项目打包一下看看这两个组件的style合并之后长啥样。
可以看到当前只存在一个 .demo 的类名,但是这个类名中存在两个 background 属性,按照顺序也就是 Student 组件的样式 和 School 组件的样式。
为了避免这个问题, Vue 使用了 scoped 属性,来作为样式隔离。
Vue scoped 样式隔离
先说使用方法,只需要在组件中的 <style></style> 标签中加入 scoped 属性即可,Vue 会自动处理样式,就像下面这样。
<style scoped>
.demo {
background:red
}
</style>
我在 School 组件 和 Student 组件 中都加上这个属性,然后再来看看页面展示效果
打包看下编译后的 css 文件长啥样。
.demo[data-v-2578f57b]{background:orange}
.demo[data-v-5c5da36a]{background:red}
存在两个 demo 类名,但是两个 demo 类名上都新增了一个 [data-v-xxxx] 的属性,这个就是Vue 底层根据我们的 scopde 属性,针对这些样式做了处理,导致在编译的时候,这些样式不被认为是同名样式,所以不会导致样式覆盖。
再来看看控制台上的 DOM节点和样式
可以看到控制台上的样式,和 打包之后的样式是一致的,但是在 DOM 节点上,我们可以发现,两个组件的根元素和子元素上,都添加了一个 data-v-xxxx 属性,而且还是相同的。
这是因为 Vue有一个 自带的插件 Vue-loader,通过这个插件,就可以实现这些转化,而且不用引入别的第三方包。( Vue-loader 使用的是 Post-CSS 来实现这些转换,在深入的源码研究就暂时不说了,我也还没看到那里去。。。 )
Scoped CSS 和 CSS Modules
一个是在编译之后,给类名上加上属性选择器的同时,还需要给 DOM 节点增加自定义属性用来隔离样式 -- Scoped CSS
一个是在编译之后,直接在 DOM 节点上给类名增加一个hash后缀,不会在节点上增加自定义属性用来隔离样式,且DOM节点与样式文件展示类名一致 -- CSS Modules。
这样一对比,发现 CSS Modules 是更好用一点哈。所以在 Vue Loader v15 及以上版本 增加了
CSS Modules 功能,但是需要手动开启该功能
CSS Modules 必须通过向 css-loader
传入 modules: true
来开启
// webpack.config.js
{
module: {
rules: [
// ... 其它规则省略
{
test: /\.css$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
// 开启 CSS Modules
modules: true,
// 自定义生成的类名
localIdentName: '[local]_[hash:base64:8]'
}
}
]
}
]
}
}
然后在你的 <style>
上添加 module
特性,就和添加 scoped 属性一样
<style module>
.red {
color: red;
}
.bold {
font-weight: bold;
}
</style>
总结
Vue scoped 作用
-
给当前组件的所有标签添加一个不重复的data属性(形如:
data-v-40b76154
)来表示唯一性 -
为了避免影响全局样式,给每句CSS选择器末尾(编译后生成的CSS语句)加一个当前组件的
data-v-hash
属性选择器来私有化样式 - 为了避免影响子组件的样式,只会给子组件的最外层标签上添加当前组件的
data-v-hash
属性
CSS Modules 模块化应用越来越好,也越来越广泛,我觉得还是 CSS Modules 比较好,至少看起来比较清爽一点,不会在节点上增加很多自定义属性