vue 父组件样式污染子组件

477 字
2 分钟
vue 父组件样式污染子组件

现象#

父组件添加 scoped 样式,对子组件同类名元素生效导致子组件样式被污染

原因#

当子组件根元素类名和父组件某一元素类名相同时,父组件类名样式即使添加了 scoped,也会对子组件根元素生效

根据查看 dom 结构及样式,发现被污染的 dom 上存在两个 data-v-id,并且污染样式来源于父组件同类名元素

vue-scoped-style
vue-scoped-style

对 vue 子组件根节点自动继承父组件 id 的行为,官方文档有做出说明

使用 scoped,父组件的样式不会泄漏到子组件中。但是,子组件的根节点将同时受到父级作用域 CSS 和子级作用域 CSS 的影响。这是设计使然,以便父级可以设置子根元素的样式以实现布局目的

vue-loader 官方文档文档中也有给出同样的说明,但是该逻辑实际是在 vue 的 render 阶段而非编译阶段,具体代码在 vue 源码 renderElementVNode 方法中

core-main/packages/server-renderer/src/render.ts
function renderElementVNode(
push: PushFn,
vnode: VNode,
parentComponent: ComponentInternalInstance,
slotScopeId: string | undefined,
) {
const tag = vnode.type as string
let { props, children, shapeFlag, scopeId, dirs } = vnode
let openTag = `<${tag}`
...
if (scopeId) {
openTag += ` ${scopeId}`
}
// inherit parent chain scope id if this is the root node
let curParent: ComponentInternalInstance | null = parentComponent
let curVnode = vnode
while (curParent &amp;&amp; curVnode === curParent.subTree) {
curVnode = curParent.vnode
if (curVnode.scopeId) {
openTag += ` ${curVnode.scopeId}`
}
curParent = curParent.parent
}
if (slotScopeId) {
openTag += ` ${slotScopeId}`
}
push(openTag + `>`)
...
}

该部分代码在渲染子组件时会判断当前元素是否是子组件根节点,如果是则会将自己的 scopeId 和父组件 scopeId 一并添加到元素属性上,这里的 scopeId 就是浏览器控制台中看到的 dom 中的 data-v-xx

解决方案#

父组件元素与子组件根节点避免同名、开发中慎用单字类名

vue 父组件样式污染子组件
https://wangxiang.website/posts/前端框架/vue-scoped-issue/
作者
翔子
发布于
2024-02-24
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
翔子
前端开发工程师
公告
博客已从 VitePress 迁移到 Astro + Firefly 主题,223 篇文章全部保留。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
221
分类
9
标签
28
总字数
411,914
运行时长
0
最后活动
0 天前

文章目录