uni-app 小程序支持 teleport 了

0 阅读2分钟

前言

在 Vue 里写弹窗、抽屉、Toast、ActionSheet 这类组件时,teleport 是非常常见的能力。它可以把内容渲染到当前组件层级之外,避免被父级的 overflowz-index 或样式隔离影响。

但小程序不是浏览器,没有真正意义上的 document.body,也不能像 Web 一样随意移动 DOM 节点。所以以前做跨端组件时,经常需要这样写:

<!-- #ifdef H5 -->
<teleport to="body">
  <popup />
</teleport>
<!-- #endif -->

<!-- #ifdef MP-WEIXIN -->
<root-portal>
  <popup />
</root-portal>
<!-- #endif -->

5.14 开始,小程序端支持直接写 teleport 了。开发者不再需要为了 H5 和小程序写两套条件编译,统一使用 Vue 的写法即可。

<teleport to="body">
  <popup />
</teleport>

在 H5 端,它还是 Vue 原生的 teleport;在支持的小程序端,uni-app 会自动把它转换成平台原生的 root-portal

支持平台

目前支持的平台有:

  • 微信小程序
  • 支付宝小程序
  • 京东小程序

使用方式

用法和 Vue 保持一致:

<template>
  <view class="page">
    <button @click="show = true">打开弹窗</button>

    <teleport to="body">
      <view v-if="show" class="mask" @click="show = false">
        <view class="dialog" @click.stop>
          我是弹窗内容
        </view>
      </view>
    </teleport>
  </view>
</template>

<script setup>
import { ref } from 'vue'

const show = ref(false)
</script>

编译到支持的小程序平台后,模板会变成类似这样:

<root-portal>
  <view class="mask">
    <view class="dialog">
      我是弹窗内容
    </view>
  </view>
</root-portal>

也就是说,开发者写的是 teleport,小程序真正运行的是 root-portal

实现原理

这次支持本质上是一个编译期转换。

uni-app 在小程序编译阶段会识别模板里的 teleport 标签,并把它转换成 root-portal。这样既保留了 Vue 的开发体验,又能利用小程序平台已有的原生能力。

整体可以理解为:

<teleport to="body">
  <view />
</teleport>

编译为:

<root-portal>
  <view />
</root-portal>

需要注意的是,小程序的 root-portal 和 Vue 的 teleport 并不是完全等价的能力。Vue 的 teleport 可以通过 to 指定目标节点,而小程序的 root-portal 是把内容提升到页面根节点下渲染,并不支持传送到任意位置。

所以 uni-app 做的是能力映射,而不是在小程序里完整模拟 Web Teleport。

属性差异

虽然用法统一成了 teleport,但小程序端最终还是会落到 root-portal,所以部分属性会有差异。

to 和 defer

todefer 都是 Vue Teleport 的属性,但小程序 root-portal 没有对应能力,所以编译到小程序端时会被移除。

<teleport to="body" defer>
  <popup />
</teleport>

会被转换成类似:

<root-portal>
  <popup />
</root-portal>

如果代码同时跑 H5 和小程序,可以继续保留这些属性:to="body"defer 在 H5 端仍然有意义,只是在小程序端不会生效。

disabled

Vue Teleport 使用 disabled 控制是否禁用传送:

<teleport :disabled="disabled">
  <popup />
</teleport>

小程序 root-portal 使用的是 enable,语义正好相反。因此编译到小程序时,uni-app 会自动做一次取反转换。

例如:

<teleport :disabled="disabled">
  <popup />
</teleport>

会被处理成类似:

<root-portal :enable="!disabled">
  <popup />
</root-portal>

开发者仍然按照 Vue 的 disabled 语义写即可,不需要手动改成 enable