扒一扒element的源码来看popover的reference插槽默认只渲染第一个dom描述 今天被问了一个问题说,v
描述
今天被问了一个问题说,vue slot,放在写死的dom前面,写死的dom就会不展示,放在后面就会正常展示 什么原因?
一开始没看懂问题,觉得slot插槽那不是你写什么就渲染什么吗,怎么还能没渲染,然后就给出了下面的demo
基本代码
<script setup>
import { ref } from 'vue';
import { ElPopover } from 'element-plus';
import 'element-plus/dist/index.css';
const props = defineProps({
show: Boolean,
});
</script>
<template>
<div>
<el-popover
placement="bottom"
:width="216"
trigger="click"
:visible="visible"
ref="reportPopoverDom"
:append-to="props.parentDom"
:popper-style="props.data?.correct_result === 0 ? { top: 'auto' } : {}"
>
<template #reference>
<span> showSpan7 </span>
<span>
<slot name="clickArea"></slot>
</span>
</template>
<div>popover</div>
</el-popover>
</div>
</template>
问题代码
<template #reference>
<span>
<slot name="clickArea"></slot>
</span>
<span> showSpan7 </span>
</template>
这个时候就没展示showSpan7,放到前面去,又展示了,然后又加了一些其他的dom,也没有展示,其实大概能猜出来是默认渲染了第一个节点了。如果你只想要解决方法,那就直接把俩个元素包起来即可。
解决方案
<template #reference>
<div>
<span>
<slot name="clickArea"></slot>
</span>
<span> showSpan7 </span>
</div>
</template>
查找原因
这个时候去找源码就比较明确,就找什么时候做到的只渲染第一个节点 就在popper里的trigger找到如下代码
only-child组件
import {
Comment,
Fragment,
Text,
cloneVNode,
defineComponent,
inject,
withDirectives,
} from 'vue'
import { NOOP, debugWarn, isObject } from '@element-plus/utils'
import {
FORWARD_REF_INJECTION_KEY,
useForwardRefDirective,
useNamespace,
} from '@element-plus/hooks'
import type { Ref, VNode } from 'vue'
const NAME = 'ElOnlyChild'
export const OnlyChild = defineComponent({
name: NAME,
setup(_, { slots, attrs }) {
const forwardRefInjection = inject(FORWARD_REF_INJECTION_KEY)
const forwardRefDirective = useForwardRefDirective(
forwardRefInjection?.setForwardRef ?? NOOP
)
return () => {
const defaultSlot = slots.default?.(attrs)
if (!defaultSlot) return null
if (defaultSlot.length > 1) {
debugWarn(NAME, 'requires exact only one valid child.')
return null
}
const firstLegitNode = findFirstLegitChild(defaultSlot)
if (!firstLegitNode) {
debugWarn(NAME, 'no valid child node found')
return null
}
return withDirectives(cloneVNode(firstLegitNode!, attrs), [
[forwardRefDirective],
])
}
},
})
function findFirstLegitChild(node: VNode[] | undefined): VNode | null {
if (!node) return null
const children = node as VNode[]
for (const child of children) {
/**
* when user uses h(Fragment, [text]) to render plain string,
* this switch case just cannot handle, when the value is primitives
* we should just return the wrapped string
*/
if (isObject(child)) {
switch (child.type) {
case Comment:
continue
case Text:
case 'svg':
return wrapTextContent(child)
case Fragment:
return findFirstLegitChild(child.children as VNode[])
default:
return child
}
}
return wrapTextContent(child)
}
return null
}
function wrapTextContent(s: string | VNode) {
const ns = useNamespace('only-child')
return <span class={ns.e('content')}>{s}</span>
}
export type OnlyChildExpose = {
forwardRef: Ref<HTMLElement>
}
读完代码就知道大概是以下步骤
获取默认插槽内容 slots.default?.(attrs)
。
如果插槽内容长度超过1,则发出警告并返回 null
。
使用 findFirstLegitChild
函数找到第一个有效的子节点。
如果没有找到有效子节点,则发出警告并返回 null
。
使用 withDirectives
和 cloneVNode
对第一个有效子节点进行处理,并应用 forwardRefDirective
指令。
然后看还有哪些地方使用
发现大部分trigger弹窗地方还是在使用的,所以后面再遇到也知道了
转载自:https://juejin.cn/post/7418676793076973579