Ant design vue 中 Select 组件的getPopupContainer和Open属性问题

269 阅读3分钟

随手记录,虚心求教。

版本

  • node:18.14.0
  • npm:9.6.0
  • vue:3.3.4
  • ant-design-vue: 4.0.2

问题复现

代码如下:

<script setup lang="ts">
const getPopupContainer = (triggerNode: Node) => triggerNode.parentElement?.parentElement || document.body

</script>
<template>
    <div class="select-filter-wrapper">
        <div class="filter-wrapper">
            <a-select
                style="width: 120px"
                value="全部类型"
                defaultOpen
                class="type-select"
                :getPopupContainer="getPopupContainer"
            >
                <a-select-option value="jack">Jack</a-select-option>
                <a-select-option value="lucy">Lucy</a-select-option>

                <a-select-option value="Yiminghe">yiminghe</a-select-option>
            </a-select>
        </div>
    </div>
</template>

如果不加getPopupContainer属性,那么下拉框将会绑定到body元素下,而加了getPopupContainer属性(值为函数)后,将会绑定到返回值(类型是DOM)上。 getPopupContainer属性函数的参数是trggerNode,即触发下拉框显示的元素,即:

image.png 如果,我想将其挂载到.ant-select元素上,就需要使用triggerNode.parentElement;如果我想挂载到和.ant-select同级的位置

image.png 那可能下意识写成了triggerNode.parentElement.parentElement

然而这里有一个问题

triggerNode.parentElement.parentElement在第一次执行时(注:执行,而非渲染到界面上)值为null

其原因可能是,第一次执行时,vue在执行_createVNode的时候,整个Select组件虽然渲染了自己的DOM结构,还没有挂载到网页的Dom树上,这就是为什么triggerNode.parentElement取到了.ant-select(因为还在Select组件自己的DOM结构中),而triggerNode.parentElement.parentElement取到的是null(因为还没挂载到网页整体的DOM树上,.ant-select还没有父节点)。

影响

当像上述代码展示那样,我想让下拉框和Select组件同级,并且加个兜底选项document.body保证下拉框能够有地方挂载。 此时,如果我加了defaultOpen或者open属性,那么就会在第一次执行后就要渲染到界面上,可是由于triggerNode.parentElement.parentElement取到的是null,那么getPopupContainer的返回值就成了document.body了,即会直接渲染到body元素上。即使后续执行时Select组件已经挂载到网页的DOM树上了,再执行triggerNode.parentElement.parentElement可以顺利拿到.filter-wrapper元素,也无法改变挂载位置。

而如果不加defaultOpen或者open属性,那么只有在第一次触发下拉框时渲染,那此时Select组件已经挂载到网页的DOM树上了,此时再执行triggerNode.parentElement.parentElement时可以顺利拿到.filter-wrapper元素。就能顺利使得下拉框和Select组件同级

说白了,就是虚拟DOM的基本知识和小细节,执行和渲染

总结

其实也不是什么深入的问题,只是小细节加深一下对虚拟DOM和Vue的认知。getPopupContainer函数可以直接使用document.querySelector()便能规避这种问题。

延申内容

假设上述代码改为:

const getPopupContainer = (triggerNode: Node) => triggerNode.parentElement?.parentElement

就是去掉兜底选项,也能顺利使得下拉框和Select组件同级,当然这里并不是因为第一次执行triggerNode.parentElement.parentElement时可以顺利拿到.filter-wrapper元素,而是加了defaultOpen之后,执行了三次,第一次执行时因为未挂载而找不到.filter-wrapper,但是第二次执行时已经挂载好了,所以找到了.filter-wrapper。如果添加兜底,虽然也会执行三次,但是第一次由于已经得到了body,后续即使得到.filter-wrapper,也会被忽略。

预留问题

  1. 为什么加了defaultOpen / open后会执行三次。

  2. 涉及到的文件:

    • select/index.js
    • vc-select/Select.js
    • vc-select/BaseSelect.js
    • vc-select/SelectTrigger.js
    • vc-trigger/Trigger.js,

    其中,vc-trigger/Trigger.js里面的

image.png

红框部分为啥没执行,整个函数作用是啥。