Vue.js中Scoped样式意外渗透到递归子组件问题咨询
Vue递归组件中Scoped样式意外渗透的成因分析
问题描述
在Vue.js递归组件场景下,仅为父级Comp组件设置data-border和class-border属性,但其对应的边框样式却意外作用到子级Comp组件——子元素本不应显示边框,实际却出现了边框。目前通过将选择器[data-border] li替换为更严格的[data-border] > ul > li可解决问题,但按认知Scoped样式不应出现此情况,现分析问题成因。
代码示例
App.vue
<template> <Comp :items="items" :has-border="true"></Comp> </template> <script setup> import Comp from './Comp.vue'; const items = [ { name: 'parent', hasBorder: true, children: [ { name: 'child', hasBorder: false, children: [] } ] } ] </script>
Comp.vue
<template> <div v-if="items && items.length" class="comp" :class="{ 'class-border': hasBorder }" :data-border="hasBorder ? '' : null" > <ul v-for="(item, index) in items" :key="index"> <li> {{ item.name }} <Comp :items="item.children"></Comp> </li> </ul> </div> </template> <script lang="ts" setup> import { defineProps } from "vue"; const { items, hasBorder } = defineProps<{ items?: any[]; hasBorder?: boolean; }>(); </script> <style scoped> .comp { margin-left: 1rem; padding: 3rem; } li { outline: 1px dotted grey; } [data-border] li { border-left: 5px solid red; } .class-border li { border-top: 5px solid blue; } </style>
成因分析
- Vue Scoped样式的核心逻辑:Vue会给Scoped样式所属组件的根元素及模板内渲染的所有元素添加唯一的
data-v-xxx属性,同时将样式选择器自动拼接该属性,以此限制样式仅作用于当前组件的DOM范围。 - 后代选择器的匹配特性:你使用的
[data-border] li是后代选择器,它会匹配[data-border]元素下所有层级的li元素,包括子级Comp组件渲染出的li。 - 子组件DOM的归属:由于子级Comp是嵌套在父级Comp的
<li>内部渲染的,子组件的DOM节点属于父级Comp模板渲染出的DOM树的一部分,因此会被添加父级组件的data-v-xxx属性。父级Scoped样式中的[data-border] li会被编译为[data-border][data-v-xxx] li[data-v-xxx],自然会匹配到子组件内的li元素,导致样式渗透。
解决方案原理
使用直接子选择器[data-border] > ul > li时,只会匹配[data-border]元素下直接子元素<ul>中的直接子元素<li>,不会深入匹配子组件内部的<li>——因为子组件的<li>是在子组件的根<div>之下,并非父级<ul>的直接子元素,因此不会被该选择器命中,从而避免了样式渗透。
内容的提问来源于stack exchange,提问作者Eugene Barsky
相关产品推荐
相关产品推荐

