mirror of https://github.com/vuejs/core.git
257 lines
7.2 KiB
TypeScript
257 lines
7.2 KiB
TypeScript
import { type ComponentInternalInstance, currentInstance } from './component'
|
|
import {
|
|
type VNode,
|
|
type VNodeChild,
|
|
type VNodeNormalizedChildren,
|
|
normalizeVNode,
|
|
} from './vnode'
|
|
import {
|
|
EMPTY_OBJ,
|
|
type IfAny,
|
|
type Prettify,
|
|
ShapeFlags,
|
|
SlotFlags,
|
|
def,
|
|
isArray,
|
|
isFunction,
|
|
} from '@vue/shared'
|
|
import { warn } from './warning'
|
|
import { isKeepAlive } from './components/KeepAlive'
|
|
import {
|
|
type ContextualRenderFn,
|
|
currentRenderingInstance,
|
|
withCtx,
|
|
} from './componentRenderContext'
|
|
import { isHmrUpdating } from './hmr'
|
|
import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
|
|
import { TriggerOpTypes, trigger } from '@vue/reactivity'
|
|
import { createInternalObject } from './internalObject'
|
|
|
|
export type Slot<T extends any = any> = (
|
|
...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
|
|
) => VNode[]
|
|
|
|
export type InternalSlots = {
|
|
[name: string]: Slot | undefined
|
|
}
|
|
|
|
export type Slots = Readonly<InternalSlots>
|
|
|
|
declare const SlotSymbol: unique symbol
|
|
export type SlotsType<T extends Record<string, any> = Record<string, any>> = {
|
|
[SlotSymbol]?: T
|
|
}
|
|
|
|
export type StrictUnwrapSlotsType<
|
|
S extends SlotsType,
|
|
T = NonNullable<S[typeof SlotSymbol]>,
|
|
> = [keyof S] extends [never] ? Slots : Readonly<T> & T
|
|
|
|
export type UnwrapSlotsType<
|
|
S extends SlotsType,
|
|
T = NonNullable<S[typeof SlotSymbol]>,
|
|
> = [keyof S] extends [never]
|
|
? Slots
|
|
: Readonly<
|
|
Prettify<{
|
|
[K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any
|
|
? T[K]
|
|
: Slot<T[K]>
|
|
}>
|
|
>
|
|
|
|
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
|
|
/**
|
|
* cache indexes for slot content
|
|
* @internal
|
|
*/
|
|
__?: number[]
|
|
}
|
|
|
|
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 => {
|
|
if ((rawSlot as any)._n) {
|
|
// already normalized - #5353
|
|
return rawSlot as Slot
|
|
}
|
|
const normalized = withCtx((...args: any[]) => {
|
|
if (
|
|
__DEV__ &&
|
|
currentInstance &&
|
|
!(ctx === null && currentRenderingInstance) &&
|
|
!(ctx && ctx.root !== currentInstance.root)
|
|
) {
|
|
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(...args))
|
|
}, ctx) as Slot
|
|
// NOT a compiled slot
|
|
;(normalized as ContextualRenderFn)._c = false
|
|
return normalized
|
|
}
|
|
|
|
const normalizeObjectSlots = (
|
|
rawSlots: RawSlots,
|
|
slots: InternalSlots,
|
|
instance: ComponentInternalInstance,
|
|
) => {
|
|
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__ &&
|
|
isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance)
|
|
)
|
|
) {
|
|
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) &&
|
|
!(__COMPAT__ && isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance))
|
|
) {
|
|
warn(
|
|
`Non-function value encountered for default slot. ` +
|
|
`Prefer function slots for better performance.`,
|
|
)
|
|
}
|
|
const normalized = normalizeSlotValue(children)
|
|
instance.slots.default = () => normalized
|
|
}
|
|
|
|
const assignSlots = (
|
|
slots: InternalSlots,
|
|
children: Slots,
|
|
optimized: boolean,
|
|
) => {
|
|
for (const key in children) {
|
|
// #2893
|
|
// when rendering the optimized slots by manually written render function,
|
|
// do not copy the `slots._` compiler flag so that `renderSlot` creates
|
|
// slot Fragment with BAIL patchFlag to force full updates
|
|
if (optimized || !isInternalKey(key)) {
|
|
slots[key] = children[key]
|
|
}
|
|
}
|
|
}
|
|
|
|
export const initSlots = (
|
|
instance: ComponentInternalInstance,
|
|
children: VNodeNormalizedChildren,
|
|
optimized: boolean,
|
|
): void => {
|
|
const slots = (instance.slots = createInternalObject())
|
|
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
|
|
const type = (children as RawSlots)._
|
|
if (type) {
|
|
assignSlots(slots, children as Slots, optimized)
|
|
// make compiler marker non-enumerable
|
|
if (optimized) {
|
|
def(slots, '_', type, true)
|
|
}
|
|
} else {
|
|
normalizeObjectSlots(children as RawSlots, slots, instance)
|
|
}
|
|
} else if (children) {
|
|
normalizeVNodeSlots(instance, children)
|
|
}
|
|
}
|
|
|
|
export const updateSlots = (
|
|
instance: ComponentInternalInstance,
|
|
children: VNodeNormalizedChildren,
|
|
optimized: boolean,
|
|
): void => {
|
|
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
|
|
assignSlots(slots, children as Slots, optimized)
|
|
trigger(instance, TriggerOpTypes.SET, '$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.
|
|
assignSlots(slots, children as Slots, optimized)
|
|
}
|
|
} else {
|
|
needDeletionCheck = !(children as RawSlots).$stable
|
|
normalizeObjectSlots(children as RawSlots, slots, instance)
|
|
}
|
|
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) && deletionComparisonTarget[key] == null) {
|
|
delete slots[key]
|
|
}
|
|
}
|
|
}
|
|
}
|