新聞中心
Vue3 的組合式 API 以及基于 Proxy 響應式原理已經(jīng)有很多文章介紹過了,除了這些比較亮眼的更新,Vue3 還新增了一個內(nèi)置組件:Teleport。這個組件的作用主要用來將模板內(nèi)的 DOM 元素移動到其他位置。

使用場景
業(yè)務開發(fā)的過程中,我們經(jīng)常會封裝一些常用的組件,例如 Modal 組件。相信大家在使用 Modal 組件的過程中,經(jīng)常會遇到一個問題,那就是 Modal 的定位問題。
話不多說,我們先寫一個簡單的 Modal 組件。
彈窗標題
- x
- 彈窗文本內(nèi)容
然后我們在頁面中引入 Modal 組件。
Modal
如上圖所示, div.container 下彈窗組件正常展示。使用 fixed 進行布局的元素,在一般情況下會相對于屏幕視窗來進行定位,但是如果父元素的 transform, perspective 或 filter 屬性不為 none 時,fixed 元素就會相對于父元素來進行定位。
我們只需要把 .container 類的 transform 稍作修改,彈窗組件的定位就會錯亂。
Modal
這個時候,使用 Teleport 組件就能解決這個問題了。
“Teleport 提供了一種干凈的方法,允許我們控制在 DOM 中哪個父節(jié)點下呈現(xiàn) HTML,而不必求助于全局狀態(tài)或?qū)⑵洳鸱譃閮蓚€組件。-- Vue 官方文檔
我們只需要將彈窗內(nèi)容放入 Teleport 內(nèi),并設置 to 屬性為 body,表示彈窗組件每次渲染都會做為 body 的子級,這樣之前的問題就能得到解決。
- ...
可以在 https://codesandbox.io/embed/vue-modal-h5g8y 查看代碼。
使用 Teleport 的 Modal
源碼解析
我們可以先寫一個簡單的模板,然后看看 Teleport 組件經(jīng)過模板編譯后,生成的代碼。
- Vue.createApp({
- template: `
teleport to body- `
- })
模板編譯后的代碼
簡化后代碼:
- function render(_ctx, _cache) {
- with (_ctx) {
- const { createVNode, openBlock, createBlock, Teleport } = Vue
- return (openBlock(), createBlock(Teleport, { to: "body" }, [
- createVNode("div", null, " teleport to body ", -1 /* HOISTED */)
- ]))
- }
- }
可以看到 Teleport 組件通過 createBlock 進行創(chuàng)建。
- // packages/runtime-core/src/renderer.ts
- export function createBlock(
- type, props, children, patchFlag
- ) {
- const vnode = createVNode(
- type,
- props,
- children,
- patchFlag
- )
- // ... 省略部分邏輯
- return vnode
- }
- export function createVNode(
- type, props, children, patchFlag
- ) {
- // class & style normalization.
- if (props) {
- // ...
- }
- // encode the vnode type information into a bitmap
- const shapeFlag = isString(type)
- ? ShapeFlags.ELEMENT
- : __FEATURE_SUSPENSE__ && isSuspense(type)
- ? ShapeFlags.SUSPENSE
- : isTeleport(type)
- ? ShapeFlags.TELEPORT
- : isObject(type)
- ? ShapeFlags.STATEFUL_COMPONENT
- : isFunction(type)
- ? ShapeFlags.FUNCTIONAL_COMPONENT
- : 0
- const vnode: VNode = {
- type,
- props,
- shapeFlag,
- patchFlag,
- key: props && normalizeKey(props),
- ref: props && normalizeRef(props),
- }
- return vnode
- }
- // packages/runtime-core/src/components/Teleport.ts
- export const isTeleport = type => type.__isTeleport
- export const Teleport = {
- __isTeleport: true,
- process() {}
- }
傳入 createBlock 的第一個參數(shù)為 Teleport,最后得到的 vnode 中會有一個 shapeFlag 屬性,該屬性用來表示 vnode 的類型。isTeleport(type) 得到的結(jié)果為 true,所以 shapeFlag 屬性最后的值為 ShapeFlags.TELEPORT(1 << 6)。
- // packages/shared/src/shapeFlags.ts
- export const enum ShapeFlags {
- ELEMENT = 1,
- FUNCTIONAL_COMPONENT = 1 << 1,
- STATEFUL_COMPONENT = 1 << 2,
- TEXT_CHILDREN = 1 << 3,
- ARRAY_CHILDREN = 1 << 4,
- SLOTS_CHILDREN = 1 << 5,
- TELEPORT = 1 << 6,
- SUSPENSE = 1 << 7,
- COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
- COMPONENT_KEPT_ALIVE = 1 << 9
- }
在組件的 render 節(jié)點,會依據(jù) type 和 shapeFlag 走不同的邏輯。
- // packages/runtime-core/src/renderer.ts
- const render = (vnode, container) => {
- if (vnode == null) {
- // 當前組件為空,則將組件銷毀
- if (container._vnode) {
- unmount(container._vnode, null, null, true)
- }
- } else {
- // 新建或者更新組件
- // container._vnode 是之前已創(chuàng)建組件的緩存
- patch(container._vnode || null, vnode, container)
- }
- container._vnode = vnode
- }
- // patch 是表示補丁,用于 vnode 的創(chuàng)建、更新、銷毀
- const patch = (n1, n2, container) => {
- // 如果新舊節(jié)點的類型不一致,則將舊節(jié)點銷毀
- if (n1 && !isSameVNodeType(n1, n2)) {
- unmount(n1)
- }
- const { type, ref, shapeFlag } = n2
- switch (type) {
- case Text:
- // 處理文本
- break
- case Comment:
- // 處理注釋
- break
- // case ...
- default:
- if (shapeFlag & ShapeFlags.ELEMENT) {
- // 處理 DOM 元素
- } else if (shapeFlag & ShapeFlags.COMPONENT) {
- // 處理自定義組件
- } else if (shapeFlag & ShapeFlags.TELEPORT) {
- // 處理 Teleport 組件
- // 調(diào)用 Teleport.process 方法
- type.process(n1, n2, container...);
- } // else if ...
- }
- }
可以看到,在處理 Teleport 時,最后會調(diào)用 Teleport.process 方法,Vue3 中很多地方都是通過 process 的方式來處理 vnode 相關邏輯的,下面我們重點看看 Teleport.process 方法做了些什么。
- // packages/runtime-core/src/components/Teleport.ts
- const isTeleportDisabled = props => props.disabled
- export const Teleport = {
- __isTeleport: true,
- process(n1, n2, container) {
- const disabled = isTeleportDisabled(n2.props)
- const { shapeFlag, children } = n2
- if (n1 == null) {
- const target = (n2.target = querySelector(n2.prop.to))
- const mount = (container) => {
- // compiler and vnode children normalization.
- if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
- mountChildren(children, container)
- }
- }
- if (disabled) {
- // 開關關閉,掛載到原來的位置
- mount(container)
- } else if (target) {
- // 將子節(jié)點,掛載到屬性 `to` 對應的節(jié)點上
- mount(target)
- }
- }
- else {
- // n1不存在,更新節(jié)點即可
- }
- }
- }
其實原理很簡單,就是將 Teleport 的 children 掛載到屬性 to 對應的 DOM 元素中。為了方便理解,這里只是展示了源碼的九牛一毛,省略了很多其他的操作。
總結(jié)
希望在閱讀文章的過程中,大家能夠掌握 Teleport 組件的用法,并使用到業(yè)務場景中。盡管原理十分簡單,但是我們有了 Teleport 組件,就能輕松解決彈窗元素定位不準確的問題。
本文轉(zhuǎn)載自微信公眾號「更了不起的前端」,可以通過以下二維碼關注。轉(zhuǎn)載本文請聯(lián)系更了不起的前端公眾號。
網(wǎng)站名稱:Vue3 Teleport 組件的實踐及原理
網(wǎng)頁地址:http://fisionsoft.com.cn/article/dheihis.html


咨詢
建站咨詢
