Vue3 学习笔记 —— Teleport、keep-alive

目录

1. Teleport 传送组件

1.1 为什么要使用 Teleport

1.2 如何使用 Teleport

1.3 Teleport 源码

2. keep-alive 缓存组件

2.1 为什么要使用 keep-alive

2.2 开启 keep-alive 时的生命周期

2.3 keep-alive 使用方法

2.4 keep-alive 源码


1. Teleport 传送组件

1.1 为什么要使用 Teleport

Teleport 是 Vue3 新特性,可以把组件渲染到指定位置,类似于 React 的 Portal

假设定义了一个弹框组件,他的外容器有 position:absolute 样式,那么这个样式极其容易被父元素影响;

也就是说:如果弹窗在不同的父元素里,可能会被干扰定位,展现不同的效果;

使用 Teleport 可以把弹框组件挪动到 body 上,避免挂载到其他父节点上,同时还能使用特定页面的数据;

综上所述:在 A页面 中引入弹窗组件,弹窗组件使用 A页面 的数据,使用 Teleport 后,弹框组件 最终节点不挂载到 A页面 的容器上,而是挂载到 body 上

1.2 如何使用 Teleport

to 指的是 teleport 内包裹的内容,会被挂载到哪个元素(支持 id、class)的 同级位置

disabled 如果设为 true,则 to 属性失效


  

1.3 Teleport 源码

源码位置:core-main\packages\runtime-core\src\renderer.ts

通过 patch 函数,判断是不是 teleport 组件,如果是,则执行 process 方法

  const patch: PatchFn = (
    n1,
    n2,
    container,
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    isSVG = false,
    slotScopeIds = null,
    optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
  ) => {
    if (n1 === n2) {
      return
    }

    ...

    const { type, ref, shapeFlag } = n2
    switch (type) {
      case Text:
        ...
      case Comment:
        ...
      case Static:
        ...
      case Fragment:
        ...
      default:
        if (shapeFlag & ShapeFlags.ELEMENT) {
          ...
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          ...
          // 如果是 teleport 组件,则会调用 process 方法
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          ;(type as typeof TeleportImpl).process(
            n1 as TeleportVNode,
            n2 as TeleportVNode,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized,
            internals
          )
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
        ...
    }

    // set ref
    if (ref != null && parentComponent) {
      setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
    }
  }

下面是 创建、更新、删除逻辑:

  • 通过 resolveTarget 函数 接收 props.to 的属性,使用 querySelect 获取该属性
  • 判断是否有 disabled,如果有,则 to 属性不生效,否则将元素挂载到新的位置
  • 新节点 disabled 为 true,旧节点 disabled 为 false,就把子节点移动回容器
  • 新节点 disabled 为 false,旧节点 disabled 为 true,就把子节点移动到目标元素
  • 遍历 teleport 子节点,使用 unmount 方法去移除 
export const TeleportImpl = {
  __isTeleport: true,
  // 创建、更新
  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
  ) {
    const {
      mc: mountChildren,
      pc: patchChildren,
      pbc: patchBlockChildren,
      o: { insert, querySelector, createText, createComment }
    } = internals

    const disabled = isTeleportDisabled(n2.props)
    let { shapeFlag, children, dynamicChildren } = n2

    // #3302
    // HMR updated, force full diff
    if (__DEV__ && isHmrUpdating) {
      optimized = false
      dynamicChildren = null
    }

    if (n1 == null) {
      // 在主视图插入空白节点或空白文本
      const placeholder = (n2.el = __DEV__
        ? createComment('teleport start')
        : createText(''))
      const mainAnchor = (n2.anchor = __DEV__
        ? createComment('teleport end')
        : createText(''))
      insert(placeholder, container, anchor)
      insert(mainAnchor, container, anchor)
      // 关键代码,获取目标元素的 DOM 节点(在这里调用方法 resolveTarget 获取 to 属性值,并通过 querySelector 取到那个元素)
      const target = (n2.target = resolveTarget(n2.props, querySelector))
      const targetAnchor = (n2.targetAnchor = createText(''))
      if (target) {
        insert(targetAnchor, target)
        // #2652 we could be teleporting from a non-SVG tree into an SVG tree
        isSVG = isSVG || isTargetSVG(target)
      } else if (__DEV__ && !disabled) {
        warn('Invalid Teleport target on mount:', target, `(${typeof target})`)
      }

      // 向目标元素挂载节点
      const mount = (container: RendererElement, anchor: RendererNode) => {
        // Teleport *always* has Array children. This is enforced in both the
        // compiler and vnode children normalization.
        // 挂载子节点
        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
          mountChildren(
            children as VNodeArrayChildren,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
        }
      }

      if (disabled) {
        // 若 disabled 为 true,则挂载到原来的位置,不变化
        mount(container, mainAnchor)
      } else if (target) {
        // 若 disabled 为 false,则挂载到新的目标节点(to 属性指定的位置)
        mount(target, targetAnchor)
      }
    } else {
      // update content
      // 更新逻辑
      n2.el = n1.el
      const mainAnchor = (n2.anchor = n1.anchor)!
      const target = (n2.target = n1.target)!
      const targetAnchor = (n2.targetAnchor = n1.targetAnchor)!
      const wasDisabled = isTeleportDisabled(n1.props)
      const currentContainer = wasDisabled ? container : target
      const currentAnchor = wasDisabled ? mainAnchor : targetAnchor
      isSVG = isSVG || isTargetSVG(target)

      // 更新子节点处理 disabled 变化的逻辑
      if (dynamicChildren) {
        // fast path when the teleport happens to be a block root
        patchBlockChildren(
          n1.dynamicChildren!,
          dynamicChildren,
          currentContainer,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds
        )
        // even in block tree mode we need to make sure all root-level nodes
        // in the teleport inherit previous DOM references so that they can
        // be moved in future patches.
        traverseStaticChildren(n1, n2, true)
      } else if (!optimized) {
        patchChildren(
          n1,
          n2,
          currentContainer,
          currentAnchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          false
        )
      }

      if (disabled) {
        if (!wasDisabled) {
          // enabled -> disabled
          // move into main container
          moveTeleport(
            n2,
            container,
            mainAnchor,
            internals,
            TeleportMoveTypes.TOGGLE
          )
        }
      } else {
        // target changed
        if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) {
          const nextTarget = (n2.target = resolveTarget(
            n2.props,
            querySelector
          ))
          if (nextTarget) {
            moveTeleport(
              n2,
              nextTarget,
              null,
              internals,
              TeleportMoveTypes.TARGET_CHANGE
            )
          } else if (__DEV__) {
            warn(
              'Invalid Teleport target on update:',
              target,
              `(${typeof target})`
            )
          }
        } else if (wasDisabled) {
          // disabled -> enabled
          // move into teleport target
          moveTeleport(
            n2,
            target,
            targetAnchor,
            internals,
            TeleportMoveTypes.TOGGLE
          )
        }
      }
    }
  },

  // 移除
  remove(
    vnode: VNode,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    optimized: boolean,
    { um: unmount, o: { remove: hostRemove } }: RendererInternals,
    doRemove: Boolean
  ) {
    const { shapeFlag, children, anchor, targetAnchor, target, props } = vnode

    if (target) {
      hostRemove(targetAnchor!)
    }

    // an unmounted teleport should always remove its children if not disabled
    if (doRemove || !isTeleportDisabled(props)) {
      hostRemove(anchor!)
      if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        for (let i = 0; i < (children as VNode[]).length; i++) {
          const child = (children as VNode[])[i]
          unmount(
            child,
            parentComponent,
            parentSuspense,
            true,
            !!child.dynamicChildren
          )
        }
      }
    }
  },

  move: moveTeleport,
  hydrate: hydrateTeleport
}

参考文章:是个萌妹大佬,快关注他

学习Vue3 第十九章(Teleport传送组件)_小满zs的博客-CSDN博客Teleport Vue 3.0新特性之一。Teleport 是一种能够将我们的模板渲染至指定DOM节点,不受父级style、v-show等属性影响,但data、prop数据依旧能够共用的技术;类似于 React 的 Portal。主要解决的问题 因为Teleport节点挂载在其他指定的DOM节点下,完全不受父级style样式影响使用方法通过to 属性 插入指定元素位置 to="body" 便可以将Teleport 内容传送到指定位置https://xiaoman.blog.csdn.net/article/details/122916261

2. keep-alive 缓存组件

2.1 为什么要使用 keep-alive

不希望组件被重新渲染,影响使用体验;希望提升性能

举个栗子:两个表单切换时,默认情况下,会清空填写内容;若使用了 keep-alive,则会保留原来填写的内容

2.2 开启 keep-alive 时的生命周期

keep-alive 存在时,组件只会执行一次 onMounted 函数;不会执行 onUnMounted 函数;会反复执行 onActivated、onDeactivated 函数;具体过程如下:

  • 初次进入时,会执行 onMounted、onActivated
  • 再次进入时,只会执行 onActivated
  • 退出后只会执行 onDeactivated,不会执行 onUnMounted

注意事项:

  • 如果没有 keep-alive 组件,则不会有 onActivated 和 onDeactivated 生命周期函数
  • 只需要请求一次的接口,可以放在 onMounted 里,反复执行的,可以放在 onActivated 里
  • 卸载操作需要放在 onDeactivated 里
  • keep-alive 只能容纳一个子节点,不能容纳多个,如有多个,应该增加条件判断

2.3 keep-alive 使用方法

2.3.1 基本用法示例



  




  
  




  
    
  

2.3.1 include 和 exclude

二者可以接收三种类型的参数:用逗号分隔的字符串、正则表达式或一个数组

  • include —— 只缓存指定的组件
  • exclude —— 缓存除了指定组件之外的组件

举个栗子:

// 只缓存 A B 组件

  
  
  


// 缓存 除了 C 组件外的组件

  
  
  

2.3.2 max

最多缓存的组件数,会使用一种算法,缓存最活跃的组件们

// 最多缓存10个组件

  
  ...

2.4 keep-alive 源码

源码位置:runtime-core/src/components/KeepAlive.ts

强烈推荐直接听小满的讲解视频:

小满Vue3(第二十章 keep-alive缓存组件 & 源码解析)_哔哩哔哩_bilibili小满Vue3(第二十章 keep-alive缓存组件 & 源码解析)是Vue3 + vite + Ts + pinia + 实战 + 源码 +electron的第22集视频,该合集共计110集,视频收藏或关注UP主,及时了解更多相关视频内容。https://www.bilibili.com/video/BV1dS4y1y7vd?p=22&spm_id_from=pageDriver&vd_source=8bc01635b95dbe8ecd349b2c23b03a10

你可能感兴趣的:(Vue3,Teleport,keep-alive)