记录---Vue的scoped原理是什么?

🧑‍💻 写在开头

点赞 + 收藏 === 学会🤣🤣🤣

前言

还记得几年前我去找前端工作的时候,那时候才初入职场,有一次去面试,然后被面试官问过一个面试题,Vue中Scoped的原理是什么?幸好当时八股文面试题背的很多,我当时就说是在选择器加了一个唯一的属性实现的,那时候很慌,就怕他继续追问,在追问就答不上了,因为当时的水平也只有三板斧,就靠硬背,对知识的理解也只停留在表面,现在经过几年的开发经验后,再次回看这个问题,会有不一样的理解。

CSS常见模块化方案

  1. BEM方案:BEM全称是Block Element Modifier,通过.block__element--modifier.模块名__元素名--修饰符名这种CSS命名方式实现样式隔离和模块化;
  2. CSS Modules:将CSS文件进行编译后,使之具备模块化的能力;
  3. CSS-IN-JS:使用 js 来编写CSS规则;

而Vue设置样式的方法则是通过单文件组件中的style标签进行样式,你只要在style标签上添加一个scoped属性,就能轻松实现样式隔离,而且还可以支持lesssass等预处理器,甚至还深度集成了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中的内容最终编译出来会同时含有ParentChildscopedId,所以会同时受ParentChild两个组件的的样式影响。

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种语法:

  1. 三个大于号 >>>
  2. /deep/
  3. ::deep{}
  4. :deep()

比如你这样写了一段样式:

1
2
3
 .a :deep(.b) {
   color: green;
 }
 
上面的代码会被编译成:
 
1
2
3
 .a[data-v-9ea40744] .b {
     color: green;
 }
 
在编译后,在对应css样式上会带上该组件scoped对应的属性选择器,所以自然就能影响子组件的样式了。

 

本文转载于:https://juejin.cn/post/7447447071525748770

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

posted @   林恒  阅读(46)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2023-02-24 uni-app:获取当前经纬度解决方案+如何布置全局组件
欢迎阅读『记录---Vue的scoped原理是什么?』
点击右上角即可分享
微信分享提示