记录---Vue的scoped原理是什么?
🧑💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣
前言
还记得几年前我去找前端工作的时候,那时候才初入职场,有一次去面试,然后被面试官问过一个面试题,Vue中Scoped的原理是什么?幸好当时八股文面试题背的很多,我当时就说是在选择器加了一个唯一的属性实现的,那时候很慌,就怕他继续追问,在追问就答不上了,因为当时的水平也只有三板斧,就靠硬背,对知识的理解也只停留在表面,现在经过几年的开发经验后,再次回看这个问题,会有不一样的理解。
CSS常见模块化方案
- BEM方案:BEM全称是Block Element Modifier,通过
.block__element--modifier
即.模块名__元素名--修饰符名
这种CSS命名方式实现样式隔离和模块化; - CSS Modules:将CSS文件进行编译后,使之具备模块化的能力;
- CSS-IN-JS:使用 js 来编写CSS规则;
而Vue设置样式的方法则是通过单文件组件中的style标签进行样式,你只要在style标签上添加一个scoped
属性,就能轻松实现样式隔离,而且还可以支持less
、sass
等预处理器,甚至还深度集成了CSS Modules
。当然我们这里主要介绍是scoped
。
scoped的使用
1 2 3 4 5 | <style scoped> .container { background: red; } < /style > |
style
标签上增加scoped
属性后,最终编译出来的结果会在选择器上增加一个唯一的attribute
(比如data-v-mlxsojjm
),每个.vue
文件编译出来的attribute
都不一样,从而实现了样式隔离。1 2 3 4 5 | <style scoped> .container[data- v -mlxsojjm] { background: red; } < /style > |
.vue文件的css编译
比如你的.vue文件长这样:
1 2 3 4 5 6 7 8 9 10 11 | <template> <div class= "container" >< /div > < /template > <style scoped> .container { width: 100px; height: 100px; background-color: red; } < /style > |
我们可以用vue提供的解析单文件组件的编译包@vue/compiler-sfc
,来解析我们在.vue文件中编写的css。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | const { compileStyle } = require( "@vue/compiler-sfc" ); const css = ` .container { width: 100px; height: 100px; background-color: red; } `; const { code } = compileStyle({ source : css, // css源代码 scoped: true , // 是否要启用scoped id : `data- v -${Math.random().toString(36).substring(2, 10)}`, // scoped的 id }); console.log(code); |
编译结果如下:
1 2 3 4 5 | .container[data- v -mlxsojjm] { width: 100px; height: 100px; background-color: red; } |
可以看到,带了scoped的style标签中的css,编译后会被加上一个属性选择器,名字以data-v
开头,后面跟的是一个字符串,这个其实可以自己定义,只要保证全局唯一就行了,比如可以取当前文件的路径,然后用摘要函数md5或者sha256去生成一个哈希,取这个哈希值就行了。
而template经过编译后,结果如下:
1 2 3 | <template> <div class= "container" data- v -mlxsojjm>< /div > < /template > |
这就是scoped的原理了,通过给组件中DOM元素和CSS各自都添加一个相同且唯一的属性选择器,让当前的css文件的样式只对当前组件生效。
注意点
1. 子组件的根节点会同时被自己以及父组件的样式所影响
在vue官网中有这么一段话: “使用 scoped
后,父组件的样式将不会渗透到子组件中。不过,子组件的根节点会同时被父组件的作用域样式和子组件的作用域样式影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式” 。
啥意思呢?比如你定义一个父组件parent.vue
和子组件child.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | // Child.vue <template> <div class= "child-container" > child < /div > < /template > <style scoped> .child-container { color: red; } < /style > // parent.vue <template> <div class= "container" > <Child /> < /div > < /template > <script> import Child from "./Child.vue" ; export default { name: "Parent" , components: { Child, }, } < /script > <style scoped> .container { width: 100px; height: 100px; background-color: red; } .child-container { color: blue !important; } < /style > |
最终渲染出来的子组件里面显示的字体颜色是蓝色。
子组件Child
的根节点上既有自己声明scoped后的属性选择器,又有父级的声明scoped后的属性选择器,所以在父组件中,就可以修改子组件根节点的样式了。
我之前不知道这个知识点的时候,被这个坑了一把,不知道为啥自己组件的样式被改了,当时找了半天才看到是父组件改的,所以我之后定义组件根节点的class名字的时候,尽量定义成一个独一无二的,免得无意中被父组件的同名类名的样式污染了。
2. scoped对插槽slot的影响
我们把提供插槽的组件叫Child
,使用插槽的组件叫Parent
,slot中的内容最终编译出来会同时含有Parent
和Child
的scopedId
,所以会同时受Parent
、Child
两个组件的的样式影响。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | // Parent.vue <template> <div class= "container" > <Child > <div class= "c1" >c< /div > < /Child > < /div > < /template > <script> import Child from "./Child.vue" ; export default { name: "Parent" , components: { Child, }, } < /script > <style scoped> < /style > // Child.vue <template> <div class= "child-container" > child <div> <slot>< /slot > < /div > < /div > < /template > <style scoped> < /style > |
最终渲染的DOM如下:
如果遇到相同权重的样式,比如元素<div class="text">a</div>
,在Parent
组件中写的样式是.text{ color: red }
,在Child
组件中写的样式是.text{ color: blue }
,由于在vue父子组件的渲染过程中,子组件会先于父组件渲染完成,所以最终父组件样式会覆盖子组件相同权重的样式,最终渲染color
颜色会是red
。
深度选择器
在实际开发中,我们常常需要在父组件修改子组件的样式,比如在用三方组件库的时候,组件库里的样式往往不能100%满足我们的需求,这时候就要用到深度选择器做样式穿透了。
深度选择器有4种语法:
- 三个大于号 >>>
- /deep/
- ::deep{}
- :deep()
比如你这样写了一段样式:
1 2 3 | .a :deep(.b) { color: green; } |
1 2 3 | .a[data- v -9ea40744] .b { color: green; } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
2023-02-24 uni-app:获取当前经纬度解决方案+如何布置全局组件