Vue中Scoped的原理及深度解析

Vue中Scoped的原理及深度解析

前言

回想起几年前初入职场时,面对面试官的提问“Vue中Scoped的原理是什么?”时,我的回答虽然勉强过关,但内心却充满了不确定。那时,我对知识的理解还停留在表面,只能依靠死记硬背。如今,经过几年的开发经验积累,再次审视这个问题,我有了更深入的理解。

CSS常见模块化方案

在前端开发中,CSS模块化是一个重要的话题。常见的CSS模块化方案包括:

  • BEM方案:通过.block__element--modifier的命名方式,实现样式隔离和模块化。
  • CSS Modules:将CSS文件进行编译,使之具备模块化的能力。
  • CSS-IN-JS:使用JavaScript来编写CSS规则。

而在Vue中,通过设置单文件组件中的style标签的scoped属性,也能轻松实现样式隔离,并且支持less、sass等预处理器,甚至深度集成了CSS Modules。本文将重点介绍scoped

scoped的使用

在Vue单文件组件中,通过在style标签上添加scoped属性,可以轻松实现样式隔离。例如:

<style scoped>
  .container {
    background: red;
  }
style>

编译后,最终的结果会在选择器上增加一个唯一的属性(如data-v-mlxsojjm),从而实现了样式隔离:

<style scoped>
  .container[data-v-mlxsojjm] {
    background: red;
  }
style>
.vue文件的css编译

Vue提供了@vue/compiler-sfc包来解析单文件组件。以下是一个简单的示例,展示如何使用该包解析带有scoped属性的CSS:

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);

编译结果如下:

.container[data-v-mlxsojjm] {
  width: 100px;
  height: 100px;
  background-color: red;
}

可以看到,带有scoped属性的style标签中的CSS,编译后会被加上一个属性选择器,名字以data-v开头,后面跟的是一个字符串。这个字符串其实可以自己定义,只要保证全局唯一即可。

注意点
  1. 子组件根节点样式影响

Vue官网指出:“使用scoped后,父组件的样式将不会渗透到子组件中。不过,子组件的根节点会同时被父组件的作用域样式和子组件的作用域样式影响。”

例如,定义一个父组件parent.vue和子组件child.vue


<template>
  <div class="child-container">
    child
  div>
template>
<style scoped>
.child-container {
  color: red;
}
style>
 

<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>

最终渲染出来的子组件里面显示的字体颜色是蓝色。这是因为子组件的根节点上既有自己声明scoped后的属性选择器,又有父级的声明scoped后的属性选择器,所以在父组件中就可以修改子组件根节点的样式。

为了避免这种情况,定义组件根节点的类名时,应尽量独一无二,以免被父组件的同名类名样式污染。

  1. scoped对插槽slot的影响

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

例如:


<template>
  <div class="container">
    <Child>
      <div class="c1">cdiv>
    Child>
  div>
template>
<script>
import Child from "./Child.vue";
export default {
  name: "Parent",
  components: {
    Child,
  },
};
script>
<style scoped>
style>
 

<template>
  <div class="child-container">
    child
    <div>
      <slot>slot>
    div>
  div>
template>
<style scoped>
style>

如果遇到相同权重的样式,比如在Parent组件中写的样式是.text{ color: red },在Child组件中写的样式是.text{ color: blue },由于子组件会先于父组件渲染完成,所以最终父组件样式会覆盖子组件相同权重的样式。

深度选择器

在实际开发中,常常需要在父组件中修改子组件的样式。这时,可以使用深度选择器进行样式穿透。深度选择器有以下四种语法:

  • >>>
  • /deep/
  • ::deep{}
  • :deep()

例如:

.a :deep(.b) {
  color: green;
}

上面的代码会被编译成:

.a[data-v-9ea40744] .b {
  color: green;
}

在编译后,对应CSS样式上会带上该组件scoped对应的属性选择器,从而能够影响子组件的样式。

小结

本文主要介绍了Vue中scoped的原理及其使用注意事项。通过生成一个唯一的属性选择器来实现样式隔离是scoped的核心思想。同时,我们也需要注意子组件根节点会同时受自己和父组件样式的影响,以及scopedslot中的表现。此外,深度选择器可以帮助我们在父组件中修改子组件的样式。希望这些内容能帮助大家更好地理解和使用Vue中的scoped属性。

你可能感兴趣的:(vue.js,前端,javascript)