面试题(十三):Vue 中 scope 是怎么做的样式隔离的?

162 阅读2分钟

当你在一个 Vue 组件的 <style> 标签上添加 scoped 属性时,Vue 会自动将该样式限定在当前组件的范围内,从而防止样式冲突和不必要的样式泄漏。

实现原理

Vue 在编译带有 scoped 属性的 <style> 标签时,会按照以下步骤处理样式隔离:

  1. 生成唯一的作用域 ID:Vue 为每个带有 scoped 属性的组件生成一个唯一的作用域 ID(如 data-v-f3f3eg9)。这个 ID 是随机的,确保每个组件的作用域 ID 是独一无二的。

  2. 添加作用域 ID 到模板元素:Vue 会在编译组件模板的过程中,将这个作用域 ID 作为自定义属性添加到组件模板的所有元素上。例如,如果作用域 ID 是 data-v-l6f3f9g,那么在该组件模板的所有元素上都会添加一个属性 data-v-l6f3f9g

  3. 修改 CSS 选择器:对于组件内部的每个 CSS 规则,Vue 会自动转换其选择器,使其仅匹配带有对应作用域 ID 的元素。这是通过在 CSS 选择器的末尾添加相应的作用域 ID 属性选择器来实现的。例如,如果 CSS 规则是 .button { color: red; },并且作用域 ID 是 data-v-l6f3f9g,那么该规则会被转换成 .button[data-v-l6f3f9g] { color: red; }

示例

假设有如下 Vue 单文件组件:

<template> 
    <button class="btn">点击</button> 
</template> 

<style scoped> 
.btn { 
    background-color: red; 
} 
</style>

编译后,CSS 规则会变成类似于这样(注意:实际的作用域 ID 是随机生成的):

.btn[data-v-l6f3f9g] { 
    background-color: red; 
}

并且模板里的 <button> 元素会被编译为类似这样:

<button class="btn" data-v-l6f3f9g>点击</button>

这样,.btn 样式规则只会应用到当前组件中的 <button> 元素上,而不会影响到其他组件中的同类元素,实现了样式隔离。

注意事项

  1. 由于样式隔离是通过属性选择器和自定义属性实现的,因此这种方法的性能可能会略低于全局样式规则。
  2. scoped 样式不能影响子组件,仅限于当前的组件。如果需要影响子组件,则需要使用深度选择器(>>> 或 /deep/)。
  3. 其他 Web 组件技术如 Shadow DOM 也可以提供样式隔离的功能,但 Vue 选择了这种不需要 polyfill、兼容性更好的实现方式