当前位置:   article > 正文

Vue3 学习笔记 —— Teleport、keep-alive_invalid teleport target on mount: null

invalid teleport target on mount: null

目录

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. <teleport :disabled="false" to='body'>
  2. <A></A>
  3. </teleport>

1.3 Teleport 源码

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

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

  1. const patch: PatchFn = (
  2. n1,
  3. n2,
  4. container,
  5. anchor = null,
  6. parentComponent = null,
  7. parentSuspense = null,
  8. isSVG = false,
  9. slotScopeIds = null,
  10. optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
  11. ) => {
  12. if (n1 === n2) {
  13. return
  14. }
  15. ...
  16. const { type, ref, shapeFlag } = n2
  17. switch (type) {
  18. case Text:
  19. ...
  20. case Comment:
  21. ...
  22. case Static:
  23. ...
  24. case Fragment:
  25. ...
  26. default:
  27. if (shapeFlag & ShapeFlags.ELEMENT) {
  28. ...
  29. } else if (shapeFlag & ShapeFlags.COMPONENT) {
  30. ...
  31. // 如果是 teleport 组件,则会调用 process 方法
  32. } else if (shapeFlag & ShapeFlags.TELEPORT) {
  33. ;(type as typeof TeleportImpl).process(
  34. n1 as TeleportVNode,
  35. n2 as TeleportVNode,
  36. container,
  37. anchor,
  38. parentComponent,
  39. parentSuspense,
  40. isSVG,
  41. slotScopeIds,
  42. optimized,
  43. internals
  44. )
  45. } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
  46. ...
  47. }
  48. // set ref
  49. if (ref != null && parentComponent) {
  50. setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
  51. }
  52. }

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

  • 通过 resolveTarget 函数 接收 props.to 的属性,使用 querySelect 获取该属性
  • 判断是否有 disabled,如果有,则 to 属性不生效,否则将元素挂载到新的位置
  • 新节点 disabled 为 true,旧节点 disabled 为 false,就把子节点移动回容器
  • 新节点 disabled 为 false,旧节点 disabled 为 true,就把子节点移动到目标元素
  • 遍历 teleport 子节点,使用 unmount 方法去移除 
  1. export const TeleportImpl = {
  2. __isTeleport: true,
  3. // 创建、更新
  4. process(
  5. n1: TeleportVNode | null,
  6. n2: TeleportVNode,
  7. container: RendererElement,
  8. anchor: RendererNode | null,
  9. parentComponent: ComponentInternalInstance | null,
  10. parentSuspense: SuspenseBoundary | null,
  11. isSVG: boolean,
  12. slotScopeIds: string[] | null,
  13. optimized: boolean,
  14. internals: RendererInternals
  15. ) {
  16. const {
  17. mc: mountChildren,
  18. pc: patchChildren,
  19. pbc: patchBlockChildren,
  20. o: { insert, querySelector, createText, createComment }
  21. } = internals
  22. const disabled = isTeleportDisabled(n2.props)
  23. let { shapeFlag, children, dynamicChildren } = n2
  24. // #3302
  25. // HMR updated, force full diff
  26. if (__DEV__ && isHmrUpdating) {
  27. optimized = false
  28. dynamicChildren = null
  29. }
  30. if (n1 == null) {
  31. // 在主视图插入空白节点或空白文本
  32. const placeholder = (n2.el = __DEV__
  33. ? createComment('teleport start')
  34. : createText(''))
  35. const mainAnchor = (n2.anchor = __DEV__
  36. ? createComment('teleport end')
  37. : createText(''))
  38. insert(placeholder, container, anchor)
  39. insert(mainAnchor, container, anchor)
  40. // 关键代码,获取目标元素的 DOM 节点(在这里调用方法 resolveTarget 获取 to 属性值,并通过 querySelector 取到那个元素)
  41. const target = (n2.target = resolveTarget(n2.props, querySelector))
  42. const targetAnchor = (n2.targetAnchor = createText(''))
  43. if (target) {
  44. insert(targetAnchor, target)
  45. // #2652 we could be teleporting from a non-SVG tree into an SVG tree
  46. isSVG = isSVG || isTargetSVG(target)
  47. } else if (__DEV__ && !disabled) {
  48. warn('Invalid Teleport target on mount:', target, `(${typeof target})`)
  49. }
  50. // 向目标元素挂载节点
  51. const mount = (container: RendererElement, anchor: RendererNode) => {
  52. // Teleport *always* has Array children. This is enforced in both the
  53. // compiler and vnode children normalization.
  54. // 挂载子节点
  55. if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  56. mountChildren(
  57. children as VNodeArrayChildren,
  58. container,
  59. anchor,
  60. parentComponent,
  61. parentSuspense,
  62. isSVG,
  63. slotScopeIds,
  64. optimized
  65. )
  66. }
  67. }
  68. if (disabled) {
  69. // 若 disabled 为 true,则挂载到原来的位置,不变化
  70. mount(container, mainAnchor)
  71. } else if (target) {
  72. // 若 disabled 为 false,则挂载到新的目标节点(to 属性指定的位置)
  73. mount(target, targetAnchor)
  74. }
  75. } else {
  76. // update content
  77. // 更新逻辑
  78. n2.el = n1.el
  79. const mainAnchor = (n2.anchor = n1.anchor)!
  80. const target = (n2.target = n1.target)!
  81. const targetAnchor = (n2.targetAnchor = n1.targetAnchor)!
  82. const wasDisabled = isTeleportDisabled(n1.props)
  83. const currentContainer = wasDisabled ? container : target
  84. const currentAnchor = wasDisabled ? mainAnchor : targetAnchor
  85. isSVG = isSVG || isTargetSVG(target)
  86. // 更新子节点处理 disabled 变化的逻辑
  87. if (dynamicChildren) {
  88. // fast path when the teleport happens to be a block root
  89. patchBlockChildren(
  90. n1.dynamicChildren!,
  91. dynamicChildren,
  92. currentContainer,
  93. parentComponent,
  94. parentSuspense,
  95. isSVG,
  96. slotScopeIds
  97. )
  98. // even in block tree mode we need to make sure all root-level nodes
  99. // in the teleport inherit previous DOM references so that they can
  100. // be moved in future patches.
  101. traverseStaticChildren(n1, n2, true)
  102. } else if (!optimized) {
  103. patchChildren(
  104. n1,
  105. n2,
  106. currentContainer,
  107. currentAnchor,
  108. parentComponent,
  109. parentSuspense,
  110. isSVG,
  111. slotScopeIds,
  112. false
  113. )
  114. }
  115. if (disabled) {
  116. if (!wasDisabled) {
  117. // enabled -> disabled
  118. // move into main container
  119. moveTeleport(
  120. n2,
  121. container,
  122. mainAnchor,
  123. internals,
  124. TeleportMoveTypes.TOGGLE
  125. )
  126. }
  127. } else {
  128. // target changed
  129. if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) {
  130. const nextTarget = (n2.target = resolveTarget(
  131. n2.props,
  132. querySelector
  133. ))
  134. if (nextTarget) {
  135. moveTeleport(
  136. n2,
  137. nextTarget,
  138. null,
  139. internals,
  140. TeleportMoveTypes.TARGET_CHANGE
  141. )
  142. } else if (__DEV__) {
  143. warn(
  144. 'Invalid Teleport target on update:',
  145. target,
  146. `(${typeof target})`
  147. )
  148. }
  149. } else if (wasDisabled) {
  150. // disabled -> enabled
  151. // move into teleport target
  152. moveTeleport(
  153. n2,
  154. target,
  155. targetAnchor,
  156. internals,
  157. TeleportMoveTypes.TOGGLE
  158. )
  159. }
  160. }
  161. }
  162. },
  163. // 移除
  164. remove(
  165. vnode: VNode,
  166. parentComponent: ComponentInternalInstance | null,
  167. parentSuspense: SuspenseBoundary | null,
  168. optimized: boolean,
  169. { um: unmount, o: { remove: hostRemove } }: RendererInternals,
  170. doRemove: Boolean
  171. ) {
  172. const { shapeFlag, children, anchor, targetAnchor, target, props } = vnode
  173. if (target) {
  174. hostRemove(targetAnchor!)
  175. }
  176. // an unmounted teleport should always remove its children if not disabled
  177. if (doRemove || !isTeleportDisabled(props)) {
  178. hostRemove(anchor!)
  179. if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  180. for (let i = 0; i < (children as VNode[]).length; i++) {
  181. const child = (children as VNode[])[i]
  182. unmount(
  183. child,
  184. parentComponent,
  185. parentSuspense,
  186. true,
  187. !!child.dynamicChildren
  188. )
  189. }
  190. }
  191. }
  192. },
  193. move: moveTeleport,
  194. hydrate: hydrateTeleport
  195. }

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

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

  1. <!-- 基本用法 -->
  2. <keep-alive>
  3. <component :is="test"></component>
  4. </keep-alive>
  5. <!-- 多个条件判断的子组件 -->
  6. <keep-alive>
  7. <compA v-if="isTrue"></compA>
  8. <compB v-else></compB>
  9. </keep-alive>
  10. <!-- 和 `<transition>` 一起使用 -->
  11. <transition>
  12. <keep-alive>
  13. <A></A>
  14. </keep-alive>
  15. </transition>

2.3.1 include 和 exclude

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

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

举个栗子:

  1. // 只缓存 A B 组件
  2. <keep-alive :include="[A, B]">
  3. <A></A>
  4. <B></B>
  5. <C></C>
  6. </keep-alive>
  7. // 缓存 除了 C 组件外的组件
  8. <keep-alive :exclude="[C]">
  9. <A></A>
  10. <B></B>
  11. <C></C>
  12. </keep-alive>

2.3.2 max

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

  1. // 最多缓存10个组件
  2. <keep-alive :max="10">
  3. <A></A>
  4. ...
  5. </keep-alive>

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/70020
推荐阅读