vnode.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import {
  2. Comment,
  3. Fragment,
  4. Text,
  5. createBlock,
  6. createCommentVNode,
  7. isVNode,
  8. openBlock,
  9. } from 'vue'
  10. import { camelize, isArray } from '@vue/shared'
  11. import { hasOwn } from '../objects'
  12. import { debugWarn } from '../error'
  13. import type {
  14. VNode,
  15. VNodeArrayChildren,
  16. VNodeChild,
  17. VNodeNormalizedChildren,
  18. } from 'vue'
  19. const SCOPE = 'utils/vue/vnode'
  20. export enum PatchFlags {
  21. TEXT = 1,
  22. CLASS = 2,
  23. STYLE = 4,
  24. PROPS = 8,
  25. FULL_PROPS = 16,
  26. HYDRATE_EVENTS = 32,
  27. STABLE_FRAGMENT = 64,
  28. KEYED_FRAGMENT = 128,
  29. UNKEYED_FRAGMENT = 256,
  30. NEED_PATCH = 512,
  31. DYNAMIC_SLOTS = 1024,
  32. HOISTED = -1,
  33. BAIL = -2,
  34. }
  35. export type VNodeChildAtom = Exclude<VNodeChild, Array<any>>
  36. export type RawSlots = Exclude<
  37. VNodeNormalizedChildren,
  38. Array<any> | null | string
  39. >
  40. export function isFragment(node: VNode): boolean
  41. export function isFragment(node: unknown): node is VNode
  42. export function isFragment(node: unknown): node is VNode {
  43. return isVNode(node) && node.type === Fragment
  44. }
  45. export function isText(node: VNode): boolean
  46. export function isText(node: unknown): node is VNode
  47. export function isText(node: unknown): node is VNode {
  48. return isVNode(node) && node.type === Text
  49. }
  50. export function isComment(node: VNode): boolean
  51. export function isComment(node: unknown): node is VNode
  52. export function isComment(node: unknown): node is VNode {
  53. return isVNode(node) && node.type === Comment
  54. }
  55. const TEMPLATE = 'template'
  56. export function isTemplate(node: VNode): boolean
  57. export function isTemplate(node: unknown): node is VNode
  58. export function isTemplate(node: unknown): node is VNode {
  59. return isVNode(node) && node.type === TEMPLATE
  60. }
  61. /**
  62. * determine if the element is a valid element type rather than fragments and comment e.g. <template> v-if
  63. * @param node {VNode} node to be tested
  64. */
  65. export function isValidElementNode(node: VNode): boolean
  66. export function isValidElementNode(node: unknown): node is VNode
  67. export function isValidElementNode(node: unknown): node is VNode {
  68. return isVNode(node) && !isFragment(node) && !isComment(node)
  69. }
  70. /**
  71. * get a valid child node (not fragment nor comment)
  72. * @param node {VNode} node to be searched
  73. * @param depth {number} depth to be searched
  74. */
  75. function getChildren(
  76. node: VNodeNormalizedChildren | VNodeChild,
  77. depth: number
  78. ): VNodeNormalizedChildren | VNodeChild {
  79. if (isComment(node)) return
  80. if (isFragment(node) || isTemplate(node)) {
  81. return depth > 0 ? getFirstValidNode(node.children, depth - 1) : undefined
  82. }
  83. return node
  84. }
  85. export const getFirstValidNode = (
  86. nodes: VNodeNormalizedChildren,
  87. maxDepth = 3
  88. ) => {
  89. if (Array.isArray(nodes)) {
  90. return getChildren(nodes[0], maxDepth)
  91. } else {
  92. return getChildren(nodes, maxDepth)
  93. }
  94. }
  95. export function renderIf(
  96. condition: boolean,
  97. ...args: Parameters<typeof createBlock>
  98. ) {
  99. return condition ? renderBlock(...args) : createCommentVNode('v-if', true)
  100. }
  101. export function renderBlock(...args: Parameters<typeof createBlock>) {
  102. return openBlock(), createBlock(...args)
  103. }
  104. export const getNormalizedProps = (node: VNode) => {
  105. if (!isVNode(node)) {
  106. debugWarn(SCOPE, '[getNormalizedProps] must be a VNode')
  107. return {}
  108. }
  109. const raw = node.props || {}
  110. const type = (isVNode(node.type) ? node.type.props : undefined) || {}
  111. const props: Record<string, any> = {}
  112. Object.keys(type).forEach((key) => {
  113. if (hasOwn(type[key], 'default')) {
  114. props[key] = type[key].default
  115. }
  116. })
  117. Object.keys(raw).forEach((key) => {
  118. props[camelize(key)] = raw[key]
  119. })
  120. return props
  121. }
  122. export const ensureOnlyChild = (children: VNodeArrayChildren | undefined) => {
  123. if (!isArray(children) || children.length > 1) {
  124. throw new Error('expect to receive a single Vue element child')
  125. }
  126. return children[0]
  127. }
  128. export type FlattenVNodes = Array<VNodeChildAtom | RawSlots>
  129. export const flattedChildren = (
  130. children: FlattenVNodes | VNode | VNodeNormalizedChildren
  131. ): FlattenVNodes => {
  132. const vNodes = isArray(children) ? children : [children]
  133. const result: FlattenVNodes = []
  134. vNodes.forEach((child) => {
  135. if (isArray(child)) {
  136. result.push(...flattedChildren(child))
  137. } else if (isVNode(child) && isArray(child.children)) {
  138. result.push(...flattedChildren(child.children))
  139. } else {
  140. result.push(child)
  141. if (isVNode(child) && child.component?.subTree) {
  142. result.push(...flattedChildren(child.component.subTree))
  143. }
  144. }
  145. })
  146. return result
  147. }