componentSlots.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import { ComponentInternalInstance, currentInstance } from './component'
  2. import {
  3. VNode,
  4. VNodeNormalizedChildren,
  5. normalizeVNode,
  6. VNodeChild,
  7. InternalObjectKey
  8. } from './vnode'
  9. import {
  10. isArray,
  11. isFunction,
  12. EMPTY_OBJ,
  13. ShapeFlags,
  14. extend,
  15. def,
  16. SlotFlags
  17. } from '@vue/shared'
  18. import { warn } from './warning'
  19. import { isKeepAlive } from './components/KeepAlive'
  20. import { withCtx } from './componentRenderContext'
  21. import { isHmrUpdating } from './hmr'
  22. export type Slot = (...args: any[]) => VNode[]
  23. export type InternalSlots = {
  24. [name: string]: Slot | undefined
  25. }
  26. export type Slots = Readonly<InternalSlots>
  27. export type RawSlots = {
  28. [name: string]: unknown
  29. // manual render fn hint to skip forced children updates
  30. $stable?: boolean
  31. /**
  32. * for tracking slot owner instance. This is attached during
  33. * normalizeChildren when the component vnode is created.
  34. * @internal
  35. */
  36. _ctx?: ComponentInternalInstance | null
  37. /**
  38. * indicates compiler generated slots
  39. * we use a reserved property instead of a vnode patchFlag because the slots
  40. * object may be directly passed down to a child component in a manual
  41. * render function, and the optimization hint need to be on the slot object
  42. * itself to be preserved.
  43. * @internal
  44. */
  45. _?: SlotFlags
  46. }
  47. const isInternalKey = (key: string) => key[0] === '_' || key === '$stable'
  48. const normalizeSlotValue = (value: unknown): VNode[] =>
  49. isArray(value)
  50. ? value.map(normalizeVNode)
  51. : [normalizeVNode(value as VNodeChild)]
  52. const normalizeSlot = (
  53. key: string,
  54. rawSlot: Function,
  55. ctx: ComponentInternalInstance | null | undefined
  56. ): Slot =>
  57. withCtx((props: any) => {
  58. if (__DEV__ && currentInstance) {
  59. warn(
  60. `Slot "${key}" invoked outside of the render function: ` +
  61. `this will not track dependencies used in the slot. ` +
  62. `Invoke the slot function inside the render function instead.`
  63. )
  64. }
  65. return normalizeSlotValue(rawSlot(props))
  66. }, ctx) as Slot
  67. const normalizeObjectSlots = (rawSlots: RawSlots, slots: InternalSlots) => {
  68. const ctx = rawSlots._ctx
  69. for (const key in rawSlots) {
  70. if (isInternalKey(key)) continue
  71. const value = rawSlots[key]
  72. if (isFunction(value)) {
  73. slots[key] = normalizeSlot(key, value, ctx)
  74. } else if (value != null) {
  75. if (__DEV__) {
  76. warn(
  77. `Non-function value encountered for slot "${key}". ` +
  78. `Prefer function slots for better performance.`
  79. )
  80. }
  81. const normalized = normalizeSlotValue(value)
  82. slots[key] = () => normalized
  83. }
  84. }
  85. }
  86. const normalizeVNodeSlots = (
  87. instance: ComponentInternalInstance,
  88. children: VNodeNormalizedChildren
  89. ) => {
  90. if (__DEV__ && !isKeepAlive(instance.vnode)) {
  91. warn(
  92. `Non-function value encountered for default slot. ` +
  93. `Prefer function slots for better performance.`
  94. )
  95. }
  96. const normalized = normalizeSlotValue(children)
  97. instance.slots.default = () => normalized
  98. }
  99. export const initSlots = (
  100. instance: ComponentInternalInstance,
  101. children: VNodeNormalizedChildren
  102. ) => {
  103. if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
  104. const type = (children as RawSlots)._
  105. if (type) {
  106. instance.slots = children as InternalSlots
  107. // make compiler marker non-enumerable
  108. def(children as InternalSlots, '_', type)
  109. } else {
  110. normalizeObjectSlots(children as RawSlots, (instance.slots = {}))
  111. }
  112. } else {
  113. instance.slots = {}
  114. if (children) {
  115. normalizeVNodeSlots(instance, children)
  116. }
  117. }
  118. def(instance.slots, InternalObjectKey, 1)
  119. }
  120. export const updateSlots = (
  121. instance: ComponentInternalInstance,
  122. children: VNodeNormalizedChildren
  123. ) => {
  124. const { vnode, slots } = instance
  125. let needDeletionCheck = true
  126. let deletionComparisonTarget = EMPTY_OBJ
  127. if (vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
  128. const type = (children as RawSlots)._
  129. if (type) {
  130. // compiled slots.
  131. if (__DEV__ && isHmrUpdating) {
  132. // Parent was HMR updated so slot content may have changed.
  133. // force update slots and mark instance for hmr as well
  134. extend(slots, children as Slots)
  135. } else if (type === SlotFlags.STABLE) {
  136. // compiled AND stable.
  137. // no need to update, and skip stale slots removal.
  138. needDeletionCheck = false
  139. } else {
  140. // compiled but dynamic (v-if/v-for on slots) - update slots, but skip
  141. // normalization.
  142. extend(slots, children as Slots)
  143. }
  144. } else {
  145. needDeletionCheck = !(children as RawSlots).$stable
  146. normalizeObjectSlots(children as RawSlots, slots)
  147. }
  148. deletionComparisonTarget = children as RawSlots
  149. } else if (children) {
  150. // non slot object children (direct value) passed to a component
  151. normalizeVNodeSlots(instance, children)
  152. deletionComparisonTarget = { default: 1 }
  153. }
  154. // delete stale slots
  155. if (needDeletionCheck) {
  156. for (const key in slots) {
  157. if (!isInternalKey(key) && !(key in deletionComparisonTarget)) {
  158. delete slots[key]
  159. }
  160. }
  161. }
  162. }