componentSlots.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. import { type ComponentInternalInstance, currentInstance } from './component'
  2. import {
  3. type VNode,
  4. type VNodeChild,
  5. type VNodeNormalizedChildren,
  6. normalizeVNode,
  7. } from './vnode'
  8. import {
  9. EMPTY_OBJ,
  10. type IfAny,
  11. type Prettify,
  12. ShapeFlags,
  13. SlotFlags,
  14. def,
  15. isArray,
  16. isFunction,
  17. } from '@vue/shared'
  18. import { warn } from './warning'
  19. import { isKeepAlive } from './components/KeepAlive'
  20. import { type ContextualRenderFn, withCtx } from './componentRenderContext'
  21. import { isHmrUpdating } from './hmr'
  22. import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
  23. import { TriggerOpTypes, trigger } from '@vue/reactivity'
  24. import { createInternalObject } from './internalObject'
  25. export type Slot<T extends any = any> = (
  26. ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
  27. ) => VNode[]
  28. export type InternalSlots = {
  29. [name: string]: Slot | undefined
  30. }
  31. export type Slots = Readonly<InternalSlots>
  32. declare const SlotSymbol: unique symbol
  33. export type SlotsType<T extends Record<string, any> = Record<string, any>> = {
  34. [SlotSymbol]?: T
  35. }
  36. export type StrictUnwrapSlotsType<
  37. S extends SlotsType,
  38. T = NonNullable<S[typeof SlotSymbol]>,
  39. > = [keyof S] extends [never] ? Slots : Readonly<T> & T
  40. export type UnwrapSlotsType<
  41. S extends SlotsType,
  42. T = NonNullable<S[typeof SlotSymbol]>,
  43. > = [keyof S] extends [never]
  44. ? Slots
  45. : Readonly<
  46. Prettify<{
  47. [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any
  48. ? T[K]
  49. : Slot<T[K]>
  50. }>
  51. >
  52. export type RawSlots = {
  53. [name: string]: unknown
  54. // manual render fn hint to skip forced children updates
  55. $stable?: boolean
  56. /**
  57. * for tracking slot owner instance. This is attached during
  58. * normalizeChildren when the component vnode is created.
  59. * @internal
  60. */
  61. _ctx?: ComponentInternalInstance | null
  62. /**
  63. * indicates compiler generated slots
  64. * we use a reserved property instead of a vnode patchFlag because the slots
  65. * object may be directly passed down to a child component in a manual
  66. * render function, and the optimization hint need to be on the slot object
  67. * itself to be preserved.
  68. * @internal
  69. */
  70. _?: SlotFlags
  71. }
  72. const isInternalKey = (key: string) => key[0] === '_' || key === '$stable'
  73. const normalizeSlotValue = (value: unknown): VNode[] =>
  74. isArray(value)
  75. ? value.map(normalizeVNode)
  76. : [normalizeVNode(value as VNodeChild)]
  77. const normalizeSlot = (
  78. key: string,
  79. rawSlot: Function,
  80. ctx: ComponentInternalInstance | null | undefined,
  81. ): Slot => {
  82. if ((rawSlot as any)._n) {
  83. // already normalized - #5353
  84. return rawSlot as Slot
  85. }
  86. const normalized = withCtx((...args: any[]) => {
  87. if (
  88. __DEV__ &&
  89. currentInstance &&
  90. (!ctx || ctx.root === currentInstance.root)
  91. ) {
  92. warn(
  93. `Slot "${key}" invoked outside of the render function: ` +
  94. `this will not track dependencies used in the slot. ` +
  95. `Invoke the slot function inside the render function instead.`,
  96. )
  97. }
  98. return normalizeSlotValue(rawSlot(...args))
  99. }, ctx) as Slot
  100. // NOT a compiled slot
  101. ;(normalized as ContextualRenderFn)._c = false
  102. return normalized
  103. }
  104. const normalizeObjectSlots = (
  105. rawSlots: RawSlots,
  106. slots: InternalSlots,
  107. instance: ComponentInternalInstance,
  108. ) => {
  109. const ctx = rawSlots._ctx
  110. for (const key in rawSlots) {
  111. if (isInternalKey(key)) continue
  112. const value = rawSlots[key]
  113. if (isFunction(value)) {
  114. slots[key] = normalizeSlot(key, value, ctx)
  115. } else if (value != null) {
  116. if (
  117. __DEV__ &&
  118. !(
  119. __COMPAT__ &&
  120. isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance)
  121. )
  122. ) {
  123. warn(
  124. `Non-function value encountered for slot "${key}". ` +
  125. `Prefer function slots for better performance.`,
  126. )
  127. }
  128. const normalized = normalizeSlotValue(value)
  129. slots[key] = () => normalized
  130. }
  131. }
  132. }
  133. const normalizeVNodeSlots = (
  134. instance: ComponentInternalInstance,
  135. children: VNodeNormalizedChildren,
  136. ) => {
  137. if (
  138. __DEV__ &&
  139. !isKeepAlive(instance.vnode) &&
  140. !(__COMPAT__ && isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance))
  141. ) {
  142. warn(
  143. `Non-function value encountered for default slot. ` +
  144. `Prefer function slots for better performance.`,
  145. )
  146. }
  147. const normalized = normalizeSlotValue(children)
  148. instance.slots.default = () => normalized
  149. }
  150. const assignSlots = (
  151. slots: InternalSlots,
  152. children: Slots,
  153. optimized: boolean,
  154. ) => {
  155. for (const key in children) {
  156. // #2893
  157. // when rendering the optimized slots by manually written render function,
  158. // do not copy the `slots._` compiler flag so that `renderSlot` creates
  159. // slot Fragment with BAIL patchFlag to force full updates
  160. if (optimized || key !== '_') {
  161. slots[key] = children[key]
  162. }
  163. }
  164. }
  165. export const initSlots = (
  166. instance: ComponentInternalInstance,
  167. children: VNodeNormalizedChildren,
  168. optimized: boolean,
  169. ): void => {
  170. const slots = (instance.slots = createInternalObject())
  171. if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
  172. const type = (children as RawSlots)._
  173. if (type) {
  174. assignSlots(slots, children as Slots, optimized)
  175. // make compiler marker non-enumerable
  176. if (optimized) {
  177. def(slots, '_', type, true)
  178. }
  179. } else {
  180. normalizeObjectSlots(children as RawSlots, slots, instance)
  181. }
  182. } else if (children) {
  183. normalizeVNodeSlots(instance, children)
  184. }
  185. }
  186. export const updateSlots = (
  187. instance: ComponentInternalInstance,
  188. children: VNodeNormalizedChildren,
  189. optimized: boolean,
  190. ): void => {
  191. const { vnode, slots } = instance
  192. let needDeletionCheck = true
  193. let deletionComparisonTarget = EMPTY_OBJ
  194. if (vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
  195. const type = (children as RawSlots)._
  196. if (type) {
  197. // compiled slots.
  198. if (__DEV__ && isHmrUpdating) {
  199. // Parent was HMR updated so slot content may have changed.
  200. // force update slots and mark instance for hmr as well
  201. assignSlots(slots, children as Slots, optimized)
  202. trigger(instance, TriggerOpTypes.SET, '$slots')
  203. } else if (optimized && type === SlotFlags.STABLE) {
  204. // compiled AND stable.
  205. // no need to update, and skip stale slots removal.
  206. needDeletionCheck = false
  207. } else {
  208. // compiled but dynamic (v-if/v-for on slots) - update slots, but skip
  209. // normalization.
  210. assignSlots(slots, children as Slots, optimized)
  211. }
  212. } else {
  213. needDeletionCheck = !(children as RawSlots).$stable
  214. normalizeObjectSlots(children as RawSlots, slots, instance)
  215. }
  216. deletionComparisonTarget = children as RawSlots
  217. } else if (children) {
  218. // non slot object children (direct value) passed to a component
  219. normalizeVNodeSlots(instance, children)
  220. deletionComparisonTarget = { default: 1 }
  221. }
  222. // delete stale slots
  223. if (needDeletionCheck) {
  224. for (const key in slots) {
  225. if (!isInternalKey(key) && deletionComparisonTarget[key] == null) {
  226. delete slots[key]
  227. }
  228. }
  229. }
  230. }