componentSlots.ts 7.2 KB

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