1. Style pollution
Consider this scenario. If the same.h1 class is defined in the style of component A and component B, then the packaged CSS file will have two identical.h1 classes. When the page displays component A or component B, the style will be affected by the two defined.h1 classes, resulting in style confusion. To better illustrate, I wrote two components, A and B, with the following code:
- Component 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>
Copy the code
- The component 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>
Copy the code
When the page is opened, the color of the three H1’s is red.
In component B. H1 defines color: green; It’s covered. Open the browser console and you can see that the.h1 below overwrites the color defined by the.h1 above.
2. Use scoped
- A component
<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>
Copy the code
- B component
<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>
Copy the code
When the page opens, it looks like this:
That’s what we’re looking for. When I open the console, I see the following two changes:
- There are more CSS selectors on the package
data-v-XXXXXX
- I have a lot of DOM elements
data-v-XXXXXX
From this, we can probably guess that the style tag is scoped and compiled CSS and DOM elements in the page are appended with data-V-xxxxxx. So the browser can tell the difference between.h1 for component A and.h1 for component B.
This is using scoped to solve style contamination problems. That’s not my goal, of course, but as the title says, I want to try to clarify what scoped is and how it works. From the following questions as a breakthrough point.
- What is scopeId? How is scopeId generated?
- The SFC
template
We have not defineddata-v
So why are there more dom elements on the pagedata-v-XXXXXX
What about this property? - The SFC
style
The selector we wrote indata-v-XXXXXX
So how does that add up?
2. What is scopeId? How is scopeId generated?
Each SFC component has its own unique scopeId, similar to the scopeId in data-v-xxxxxx. As we all know, vUE single file components are processed by vue-loader. Since the scopeId of each vue file is unique, it is easy to wonder if the scopeId was generated by vue-loader. Vue-loader does have this logic.
const id = hash(
isProduction
? (shortFilePath + '\n' + source.replace(/\r\n/g.'\n'))
: shortFilePath
)
Copy the code
The code above is the code that generates the scopeId execution.
3. In SFCtemplate
We have not defineddata-v
So why are there more dom elements on the pagedata-v-XXXXXX
What about this property?
Have you taken a closer look at what the exported object looks like?
import B from "./B";
console.log('component B', B)
Copy the code
Printed results:
The exported component configuration will have a _scopeId attribute. The attribute value data-V-5277DF62 is not defined by us.
Is this the same value as the scopeId on the DOM element inside component B in the DOM screenshot above?
It is clear that the SFC component just adds the _scopeId attribute to the component configuration object at compile time. The value of the attribute is not written randomly, but is generated by vue-loader. The generation logic was covered above.
So when is scope added to the DOM element? There is a code in vue2’s patch logic inside createElm in SRC /core/vdom/patch.js. The general logic is to call setScope to append the scopeId to the DOM element after creating the actual DOM inside createElm.
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
Copy the code
The logic of setScope is as follows:
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) } }Copy the code
Data-v-xxxxxx on the DOM element is added during patch run time.
4. In SFCstyle
The selector we wrote indata-v-XXXXXX
So how does that add up?
All this is done by a loader called stylePostLoader.
- StylePostLoader handles CSS code
- CompileStyle is used internally
postCss
Transform the CSS code. You can see it in use in the screenshot belowscoped
Will use ascoped_1
The plug-in.
- To view
scoped_1
Plug-in code, found such a code.
selector.insertAfter(node, selectorParser.attribute({
attribute: id
}));
Copy the code
Data-v-xxxxxx is added to the CSS code using stylePostLoader.
5, /deep/?
ScopedId is appended to the last level of the selector by default. For example, the following example has components A and B. Component B is used in component A, when modifying the interior of component B in component A. El-autocomplete style:
.compA .el-autocomplete {
width: 280px;
}
/* Compiled code */
.compA .el-autocomplete[data-v-19388c91] {
width: 280px;
}
Copy the code
Use/deep/after
.compA /deep/.el-autocomplete {
width: 280px;
}
/* Compiled code */
.compA[data-v-19388c91] .el-autocomplete {
width: 280px;
}
Copy the code
By comparing the following DOM structure diagram, it is clear why deep works.
- use
/deep/
We wanted to change it earlierThe component B
With internal.el-autocomplete
The style of the element and the selector used after packaging is.compA .el-autocomplete[data-v-19388c91]
Obviously with.el-autocomplete
Does not exist on the elementdata-v-19388c91
Property, so styles don’t work. - use
/deep/
After? The packaged CSS looks like this.compA[data-v-19388c91] .el-autocomplete
That will bescopedId
Moved to.compA
At this point the style is in effect.
conclusion
- When styling the root node of a child component within a parent component, you do not add it
/deep/
Because there are both parent and child components on the root node of the child componentscopeId
. - This is required when styling non-root nodes of child components within the parent
/deep/
Because the last level of the parent component definition selector is used by the parent componentscopeId
The element that needs to be selected in the child component is used by the child componentscopeId
, so it won’t match, and the style won’t work.