面试官: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的使用

xml 复制代码
 <style scoped>
   .container {
       background: red;
   }
 </style>

style标签上增加scoped属性后,最终编译出来的结果会在选择器上增加一个唯一的attribute(比如data-v-mlxsojjm),每个.vue文件编译出来的attribute都不一样,从而实现了样式隔离

xml 复制代码
 <style scoped>
   .container[data-v-mlxsojjm] {
       background: red;
   }
 </style>

.vue文件的css编译

比如你的.vue文件长这样:

xml 复制代码
 <template>
   <div class="container"></div>
 </template>
 ​
 <style scoped>
 .container {
   width: 100px;
   height: 100px;
   background-color: red;
 }
 </style>

我们可以用vue提供的解析单文件组件的编译包@vue/compiler-sfc,来解析我们在.vue文件中编写的css。

javascript 复制代码
 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);

编译结果如下:

css 复制代码
 .container[data-v-mlxsojjm] {
     width: 100px;
     height: 100px;
     background-color: red;
 }

可以看到,带了scoped的style标签中的css,编译后会被加上一个属性选择器 ,名字以data-v开头,后面跟的是一个字符串,这个其实可以自己定义,只要保证全局唯一就行了,比如可以取当前文件的路径,然后用摘要函数md5或者sha256去生成一个哈希,取这个哈希值就行了。

而template经过编译后,结果如下:

xml 复制代码
 <template>
   <div class="container" data-v-mlxsojjm></div>
 </template>

这就是scoped的原理了,通过给组件中DOM元素和CSS各自都添加一个相同且唯一的属性选择器,让当前的css文件的样式只对当前组件生效

注意点

1. 子组件的根节点会同时被自己以及父组件的样式所影响

在vue官网中有这么一段话: "使用 scoped 后,父组件的样式将不会渗透到子组件中。不过,子组件的根节点会同时被父组件的作用域样式和子组件的作用域样式影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式"

啥意思呢?比如你定义一个父组件parent.vue和子组件child.vue

xml 复制代码
 // 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>

最终渲染出来的子组件里面显示的字体颜色是蓝色。

怎么会这样呢?看vue最终渲染出来DOM的样子就能看出来了。

子组件Child的根节点上既有自己声明scoped后的属性选择器,又有父级的声明scoped后的属性选择器,所以在父组件中,就可以修改子组件根节点的样式了。

我之前不知道这个知识点的时候,被这个坑了一把,不知道为啥自己组件的样式被改了,当时找了半天才看到是父组件改的,所以我之后定义组件根节点的class名字的时候,尽量定义成一个独一无二的,免得无意中被父组件的同名类名的样式污染了。

2. scoped对插槽slot的影响

我们把提供插槽的组件叫Child,使用插槽的组件叫Parent,slot中的内容最终编译出来会同时含有ParentChildscopedId,所以会同时受ParentChild两个组件的的样式影响。

javascript 复制代码
 // 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()

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

css 复制代码
 .a :deep(.b) {
   color: green;
 }

上面的代码会被编译成:

css 复制代码
 .a[data-v-9ea40744] .b {
     color: green;
 }

在编译后,在对应css样式上会带上该组件scoped对应的属性选择器,所以自然就能影响子组件的样式了。

小结

本文主要介绍了一道前端面试题Vue中scoped的原理 ,主要就是通过生成一个唯一的attribute来实现的,并且带大家通过程序编译了一下带有scoped属性css的.vue文件,另外要注意的是子组件的根节点会同时被自己以及父组件的样式所影响,以及scoped在slot中的表现!

相关推荐
测试界萧萧17 分钟前
15:00面试,15:06就出来了,问的问题有点变态。。。
自动化测试·软件测试·功能测试·程序人生·面试·职场和发展
GIS好难学1 小时前
《Vue进阶教程》第六课:computed()函数详解(上)
前端·javascript·vue.js
nyf_unknown1 小时前
(css)element中el-select下拉框整体样式修改
前端·css
m0_548514771 小时前
前端打印功能(vue +springboot)
前端·vue.js·spring boot
执键行天涯1 小时前
element-plus中的resetFields()方法
前端·javascript·vue.js
一个努力学习的小男孩1 小时前
【自学】Vues基础
vue.js
Days20501 小时前
uniapp小程序增加加载功能
开发语言·前端·javascript
喵喵酱仔__1 小时前
vue 给div增加title属性
前端·javascript·vue.js
dazhong20122 小时前
HTML前端开发-- Iconfont 矢量图库使用简介
前端·html·svg·矢量图·iconfont
m0_748248772 小时前
前端vue使用onlyoffice控件实现word在线编辑、预览(仅列出前端部分需要做的工作,不包含后端部分)
前端·vue.js·word