| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- 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<InternalSlots>
- 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__) {
- 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
- ) => {
- 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 (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)
- }
- } 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]
- }
- }
- }
- }
|