apiCreateIf.ts 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import { ELSE_IF_ANCHOR_LABEL, IF_ANCHOR_LABEL } from '@vue/shared'
  2. import { type Block, type BlockFn, insert } from './block'
  3. import { advanceHydrationNode, isHydrating } from './dom/hydration'
  4. import {
  5. insertionAnchor,
  6. insertionParent,
  7. resetInsertionState,
  8. } from './insertionState'
  9. import { renderEffect } from './renderEffect'
  10. import { DynamicFragment } from './fragment'
  11. const ifStack = [] as DynamicFragment[]
  12. const insertionParents = new WeakMap<DynamicFragment, Node[]>()
  13. /**
  14. * Collects insertionParents inside an if block during hydration
  15. * When the if condition becomes false on the client, clears the
  16. * HTML of these insertionParents to prevent duplicate rendering
  17. * results when the condition becomes true again
  18. *
  19. * Example:
  20. * const t2 = _template("<div></div>")
  21. * const n2 = _createIf(() => show.value, () => {
  22. * const n5 = t2()
  23. * _setInsertionState(n5)
  24. * const n4 = _createComponent(Comp) // renders `<span></span>`
  25. * return n5
  26. * })
  27. *
  28. * After hydration, the HTML of `n5` is `<div><span></span></div>` instead of `<div></div>`.
  29. * When `show.value` becomes false, the HTML of `n5` needs to be cleared,
  30. * to avoid duplicated rendering when `show.value` becomes true again.
  31. */
  32. export function collectInsertionParents(insertionParent: ParentNode): void {
  33. const currentIf = ifStack[ifStack.length - 1]
  34. if (currentIf) {
  35. let nodes = insertionParents.get(currentIf)
  36. if (!nodes) insertionParents.set(currentIf, (nodes = []))
  37. nodes.push(insertionParent)
  38. }
  39. }
  40. export function createIf(
  41. condition: () => any,
  42. b1: BlockFn,
  43. b2?: BlockFn,
  44. once?: boolean,
  45. elseIf?: boolean,
  46. ): Block {
  47. const _insertionParent = insertionParent
  48. const _insertionAnchor = insertionAnchor
  49. if (!isHydrating) resetInsertionState()
  50. let frag: Block
  51. if (once) {
  52. frag = condition() ? b1() : b2 ? b2() : []
  53. } else {
  54. frag =
  55. isHydrating || __DEV__
  56. ? new DynamicFragment(
  57. elseIf && isHydrating ? ELSE_IF_ANCHOR_LABEL : IF_ANCHOR_LABEL,
  58. )
  59. : new DynamicFragment()
  60. if (isHydrating) {
  61. ;(frag as DynamicFragment).teardown = () => {
  62. const nodes = insertionParents.get(frag as DynamicFragment)
  63. if (nodes) {
  64. nodes.forEach(p => ((p as Element).innerHTML = ''))
  65. insertionParents.delete(frag as DynamicFragment)
  66. }
  67. ;(frag as DynamicFragment).teardown = undefined
  68. }
  69. ifStack.push(frag as DynamicFragment)
  70. }
  71. renderEffect(() => (frag as DynamicFragment).update(condition() ? b1 : b2))
  72. isHydrating && ifStack.pop()
  73. }
  74. if (!isHydrating) {
  75. if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor)
  76. } else {
  77. // if _insertionAnchor is defined, insertionParent contains a static node
  78. // that should be skipped during hydration.
  79. // Advance to the next sibling node to bypass this static node.
  80. if (_insertionAnchor !== undefined) {
  81. advanceHydrationNode(_insertionParent!)
  82. }
  83. }
  84. return frag
  85. }