1、样式污染
考虑这样的一个场景。有组件A
和组件B
的style中定义了同样的类.h1
, 那么打包之后的css文件中就会有两个同样的.h1
类, 当页面展示组件A或者组件B的时候样式都同时受到了定义的两个.h1
的影响,造成样式错乱。为了更好说明,我写了A和B两个组件,代码如下:
- 组件A
<template>
<div class="compA">
<h1 class="h1">A Hello</h1>
<B></B>
</div>
</template>
<script>
import B from "./B";
export default {
components: {
B
}
};
</script>
<style>
.h1 {
margin: 40px 0 0;
color: red;
}
</style>
- 组件B
<template>
<div class="compB">
<h1 class="h1">B</h1>
<h1 class="h1">World</h1>
</div>
</template>
<script>
export default {};
</script>
<style>
.h1 {
margin: 40px 0 0;
color: green;
}
</style>
打开页面,此时三个h1的颜色都是红色。
在组件B中.h1
定义的color: green;
被覆盖了。
打开浏览器控制台可以看到,下面的.h1
将上面的.h1
定义的color
覆盖了。
2、使用scoped
- A组件
<template>
<div class="compA">
<h1 class="h1">A Hello</h1>
<B></B>
</div>
</template>
<script>
import B from "./B";
export default {
components: {
B
}
};
</script>
<style scoped>
.h1 {
margin: 40px 0 0;
color: red;
}
</style>
- B组件
<template>
<div class="compB">
<h1 class="h1">B</h1>
<h1 class="h1">World</h1>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
.h1 {
margin: 40px 0 0;
color: green;
}
</style>
打开页面后,是这样显示的:
这才是我们想要的效果。打开控制台,发现有下面两个变化:
- 打包后的css选择器上多了
data-v-XXXXXX
- dom元素上多了
data-v-XXXXXX
由此,我们大概能猜到style
标签上加了scoped
后编译后的css和页面中的dom元素都会加上data-v-XXXXXX
。这样浏览器就能区分A组件的.h1
和B组件的.h1
。
上面讲的是使用scoped
来解决样式污染的问题。当然这并不是我的目的,就像标题说的那样,我希望尝试把scoped
是什么以及是怎么样生效的讲清楚。从一下几个问题作为切入点。
- 什么是scopeId?scopeId是怎么生成的?
- 在sfc的
template
中我们并没有定义data-v
这样的属性, 那为什么在页面的dom元素上会多出data-v-XXXXXX
这个属性呢? - 在sfc的
style
中我们写的选择器也没有加上data-v-XXXXXX
,那这是怎么加上去的呢?
2、什么是scopeId? scopeId是怎么生成的?
每一个sfc
组件都会有自己的唯一scopeId
,类似data-v-XXXXXX
中的XXXXXX
就是scopeId。
我们都知道.vue
单文件组件是需要经过vue-loader
去处理的。既然每个vue文件的scopeId
是唯一的,那么很容易联想到scopeId
是不是vue-loader
生成的。vue-loader
里面的确有这样的逻辑。
const id = hash(
isProduction
? (shortFilePath + '\n' + source.replace(/\r\n/g, '\n'))
: shortFilePath
)
上面的代码就是生成scopeId
执行的代码。
3、在sfc的template
中我们并没有定义data-v
这样的属性, 那为什么在页面的dom元素上会多出data-v-XXXXXX
这个属性呢?
不知道大家有没有仔细看过组件导出的对象是什么样的?
import B from "./B";
console.log('组件B', B)
打印的结果:
导出的组件配置上会多一个_scopeId
属性,这个属性不是我们自己定义上去的, 属性值data-v-5277df62
是不是很熟悉?
这个值是不是和上面的dom截图中组件B内部dom元素上的scopeId
一样的?
很明显sfc组件只是在编译阶段往组件配置对象上加了_scopeId
属性, 属性值也不是随便写的, 是由vue-loader
生成的。生成逻辑上面已经讲过了。
那么dom元素是什么时候加上scope
的呢?在vue2
的patch
逻辑中有这样一段代码,这段代码在src/core/vdom/patch.js
中的createElm
内。大概的逻辑就是在createElm
内部创建真实的dom之后,调用setScope
给dom元素添加scopeId
。
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
setScope
的逻辑是这样的:
function setScope (vnode) {
let i
if (isDef(i = vnode.fnScopeId)) {
nodeOps.setStyleScope(vnode.elm, i)
} else {
let ancestor = vnode
while (ancestor) {
if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {
nodeOps.setStyleScope(vnode.elm, i)
}
ancestor = ancestor.parent
}
}
// for slot content they should also get the scopeId from the host instance.
if (isDef(i = activeInstance) &&
i !== vnode.context &&
i !== vnode.fnContext &&
isDef(i = i.$options._scopeId)
) {
nodeOps.setStyleScope(vnode.elm, i)
}
}
4、在sfc的style
中我们写的选择器也没有加上data-v-XXXXXX
,那这是怎么加上去的呢?
这一切都是由一个叫stylePostLoader
的loader来完成的。
- stylePostLoader处理css代码
- compileStyle内部使用
postCss
对css代码进行转换。下面的截图中可以看到使用了scoped
时会使用一个scoped_1
插件。
- 查看
scoped_1
插件的代码,发现有这样一段代码。
selector.insertAfter(node, selectorParser.attribute({
attribute: id
}));
5、/deep/的作用?
scopedId
默认是加在选择器的最后一级上的。比如说下面的例子中有组件A
和组件B
。在组件A
中使用了组件B
, 当在组件A中修改组件B的内部.el-autocomplete
样式时:
.compA .el-autocomplete {
width: 280px;
}
/* 编译后的代码 */
.compA .el-autocomplete[data-v-19388c91] {
width: 280px;
}
使用/deep/
后
.compA /deep/.el-autocomplete {
width: 280px;
}
/* 编译后的代码 */
.compA[data-v-19388c91] .el-autocomplete {
width: 280px;
}
对比下面的dom结构图, 很明显知道了deep之后起作用的原因了。
- 使用
/deep/
之前我们想要修改组件B
内部带有.el-autocomplete
的元素的样式,打包后使用的选择器是.compA .el-autocomplete[data-v-19388c91]
,很明显带有.el-autocomplete
的元素上是不存在data-v-19388c91
属性的,所以样式不起作用。 - 使用
/deep/
之后呢?打包后的css是这样的.compA[data-v-19388c91] .el-autocomplete
, 将scopedId
移动到了.compA
上, 此时样式是起作用的。
- 当在父组件内给子组件的根节点修改样式时,其实是不用加
/deep/
的, 因为在子组件的根节点上同时有父组件和子组件的scopeId
。 - 当在父组件内给子组件的非根节点修改样式时需要带上
/deep/
,因为在父组件定义选择器的最后一级使用的是父组件的scopeId
,而子组件中需要选择的元素却使用的是子组件的scopeId
,所以是不会匹配上的,样式也不会生效。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!