import { ComponentInternalInstance, currentInstance } from './component' import { VNode, VNodeNormalizedChildren, normalizeVNode, VNodeChild, InternalObjectKey } from './vnode' import { isArray, isFunction, EMPTY_OBJ, ShapeFlags, extend, def, SlotFlags } from '@vue/shared' import { warn } from './warning' import { isKeepAlive } from './components/KeepAlive' import { withCtx } from './componentRenderContext' import { isHmrUpdating } from './hmr' export type Slot = (...args: any[]) => VNode[] export type InternalSlots = { [name: string]: Slot | undefined } export type Slots = Readonly export type RawSlots = { [name: string]: unknown // manual render fn hint to skip forced children updates $stable?: boolean /** * for tracking slot owner instance. This is attached during * normalizeChildren when the component vnode is created. * @internal */ _ctx?: ComponentInternalInstance | null /** * indicates compiler generated slots * we use a reserved property instead of a vnode patchFlag because the slots * object may be directly passed down to a child component in a manual * render function, and the optimization hint need to be on the slot object * itself to be preserved. * @internal */ _?: SlotFlags } const isInternalKey = (key: string) => key[0] === '_' || key === '$stable' const normalizeSlotValue = (value: unknown): VNode[] => isArray(value) ? value.map(normalizeVNode) : [normalizeVNode(value as VNodeChild)] const normalizeSlot = ( key: string, rawSlot: Function, ctx: ComponentInternalInstance | null | undefined ): Slot => withCtx((props: any) => { if (__DEV__ && currentInstance) { warn( `Slot "${key}" invoked outside of the render function: ` + `this will not track dependencies used in the slot. ` + `Invoke the slot function inside the render function instead.` ) } return normalizeSlotValue(rawSlot(props)) }, ctx) as Slot const normalizeObjectSlots = (rawSlots: RawSlots, slots: InternalSlots) => { const ctx = rawSlots._ctx for (const key in rawSlots) { if (isInternalKey(key)) continue const value = rawSlots[key] if (isFunction(value)) { slots[key] = normalizeSlot(key, value, ctx) } else if (value != null) { if (__DEV__ && !__COMPAT__) { warn( `Non-function value encountered for slot "${key}". ` + `Prefer function slots for better performance.` ) } const normalized = normalizeSlotValue(value) slots[key] = () => normalized } } } const normalizeVNodeSlots = ( instance: ComponentInternalInstance, children: VNodeNormalizedChildren ) => { if (__DEV__ && !isKeepAlive(instance.vnode)) { warn( `Non-function value encountered for default slot. ` + `Prefer function slots for better performance.` ) } const normalized = normalizeSlotValue(children) instance.slots.default = () => normalized } export const initSlots = ( instance: ComponentInternalInstance, children: VNodeNormalizedChildren ) => { if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { const type = (children as RawSlots)._ if (type) { instance.slots = children as InternalSlots // make compiler marker non-enumerable def(children as InternalSlots, '_', type) } else { normalizeObjectSlots(children as RawSlots, (instance.slots = {})) } } else { instance.slots = {} if (children) { normalizeVNodeSlots(instance, children) } } def(instance.slots, InternalObjectKey, 1) } export const updateSlots = ( instance: ComponentInternalInstance, children: VNodeNormalizedChildren, optimized: boolean ) => { const { vnode, slots } = instance let needDeletionCheck = true let deletionComparisonTarget = EMPTY_OBJ if (vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { const type = (children as RawSlots)._ if (type) { // compiled slots. if (__DEV__ && isHmrUpdating) { // Parent was HMR updated so slot content may have changed. // force update slots and mark instance for hmr as well extend(slots, children as Slots) } else if (optimized && type === SlotFlags.STABLE) { // compiled AND stable. // no need to update, and skip stale slots removal. needDeletionCheck = false } else { // compiled but dynamic (v-if/v-for on slots) - update slots, but skip // normalization. extend(slots, children as Slots) // #2893 // when rendering the optimized slots by manually written render function, // we need to delete the `slots._` flag if necessary to make subsequent updates reliable, // i.e. let the `renderSlot` create the bailed Fragment if (!optimized && type === SlotFlags.STABLE) { delete slots._ } } } else { needDeletionCheck = !(children as RawSlots).$stable normalizeObjectSlots(children as RawSlots, slots) } deletionComparisonTarget = children as RawSlots } else if (children) { // non slot object children (direct value) passed to a component normalizeVNodeSlots(instance, children) deletionComparisonTarget = { default: 1 } } // delete stale slots if (needDeletionCheck) { for (const key in slots) { if (!isInternalKey(key) && !(key in deletionComparisonTarget)) { delete slots[key] } } } }