vue3 teleport 分析

892 阅读1分钟

问题来源

如果在element-plus的表格使用multi-select会神奇发现下拉被隐藏起来了,可是使用自身的select时发现下拉能正常显示。

问题定位

  1. multi-select是相对input框做绝对定位计算下拉框的, 所以外层overflow:hidden时就会展示不出。
  2. element-plus的下拉框是直接在body下面,经过计算展示了再input框下面(这里不会展开有兴趣的可以看看@popperjs/core) 我们的关注点就应该转换成为什么一个组件的编写能脱离自己的层级限定。答案:teleport

源码解析

  1. element-plus相关代码
// packages/popper/src/index.vue
export default defineComponent({
  setup(props, ctx) {
    // return usePopper(props as IPopperOptions, ctx as SetupContext)
    const popperStates = usePopper(props, ctx)
    ...
  }
  render() {
   ...
    return h(Fragment, null, [
      trigger,
      h(
        Teleport as any, // Vue did not support createVNode for Teleport
        {
          to: 'body',
          disabled: !appendToBody,
        },
        [popper],
      ),
    ])
  }
})

usePopper的抽离体现出了单一职责原则,也提高了复用度。

  1. vue-next相关代码
// packages/runtime-core/src/components/teleport.ts
export const TeleportImpl = {
 process(
     n1: TeleportVNode | null,
    n2: TeleportVNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean,
    internals: RendererInternals
 ) {
     if (n1 == null) {
      // insert anchors in the main view
      ...
      const target = (n2.target = resolveTarget(n2.props, querySelector))
      ...
      const mount = (container: RendererElement, anchor: RendererNode) => {...}
      
      if (disabled) {
        mount(container, mainAnchor)
      } else if (target) {
        mount(target, targetAnchor)
      }
      
 }
 
 // packages/runtime-dom/src/nodeOps.ts
 
const doc = (typeof document !== 'undefined' ? document : null) as Document
...
export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
  ...
  querySelector: selector => doc.querySelector(selector), // 提供方法方
  ....
}
}

其实原理很简单,就是将 Teleport 的 children 挂载到属性 to 对应的 DOM 元素(透过querySelector找到)中。


如果感兴趣你也可以亲自动手debug vue-next源码,透过跑单元测试打断点查看对应代码。更多的方法可以参考这个

最后

如果作者有啥不对可以留言,创作不易点个赞呗👍