2019-09-07 00:58:31 +08:00
|
|
|
import { ComponentInternalInstance, currentInstance } from './component'
|
2020-01-29 11:58:02 +08:00
|
|
|
import {
|
|
|
|
VNode,
|
|
|
|
VNodeNormalizedChildren,
|
|
|
|
normalizeVNode,
|
2020-04-07 09:06:48 +08:00
|
|
|
VNodeChild,
|
2020-04-16 04:45:20 +08:00
|
|
|
InternalObjectKey
|
2020-01-29 11:58:02 +08:00
|
|
|
} from './vnode'
|
2020-04-07 09:06:48 +08:00
|
|
|
import {
|
|
|
|
isArray,
|
|
|
|
isFunction,
|
|
|
|
EMPTY_OBJ,
|
|
|
|
ShapeFlags,
|
|
|
|
extend,
|
2020-07-16 08:12:49 +08:00
|
|
|
def,
|
|
|
|
SlotFlags
|
2020-04-07 09:06:48 +08:00
|
|
|
} from '@vue/shared'
|
2019-08-31 03:26:16 +08:00
|
|
|
import { warn } from './warning'
|
2019-11-26 06:34:28 +08:00
|
|
|
import { isKeepAlive } from './components/KeepAlive'
|
2021-03-06 00:10:06 +08:00
|
|
|
import { withCtx } from './componentRenderContext'
|
2020-06-13 02:53:48 +08:00
|
|
|
import { isHmrUpdating } from './hmr'
|
2019-05-31 18:07:43 +08:00
|
|
|
|
2019-11-01 23:32:53 +08:00
|
|
|
export type Slot = (...args: any[]) => VNode[]
|
2019-10-09 00:43:13 +08:00
|
|
|
|
|
|
|
export type InternalSlots = {
|
2020-02-25 12:05:35 +08:00
|
|
|
[name: string]: Slot | undefined
|
2019-10-09 00:43:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export type Slots = Readonly<InternalSlots>
|
|
|
|
|
2019-05-31 18:07:43 +08:00
|
|
|
export type RawSlots = {
|
|
|
|
[name: string]: unknown
|
2019-11-26 23:03:36 +08:00
|
|
|
// manual render fn hint to skip forced children updates
|
|
|
|
$stable?: boolean
|
2020-07-16 08:12:49 +08:00
|
|
|
/**
|
|
|
|
* for tracking slot owner instance. This is attached during
|
|
|
|
* normalizeChildren when the component vnode is created.
|
|
|
|
* @internal
|
|
|
|
*/
|
2020-03-17 00:20:52 +08:00
|
|
|
_ctx?: ComponentInternalInstance | null
|
2020-07-16 08:12:49 +08:00
|
|
|
/**
|
|
|
|
* 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
|
2019-05-31 18:07:43 +08:00
|
|
|
}
|
|
|
|
|
2020-04-07 09:06:48 +08:00
|
|
|
const isInternalKey = (key: string) => key[0] === '_' || key === '$stable'
|
|
|
|
|
2019-05-31 18:07:43 +08:00
|
|
|
const normalizeSlotValue = (value: unknown): VNode[] =>
|
|
|
|
isArray(value)
|
|
|
|
? value.map(normalizeVNode)
|
|
|
|
: [normalizeVNode(value as VNodeChild)]
|
|
|
|
|
2020-03-17 00:20:52 +08:00
|
|
|
const normalizeSlot = (
|
|
|
|
key: string,
|
|
|
|
rawSlot: Function,
|
|
|
|
ctx: ComponentInternalInstance | null | undefined
|
|
|
|
): Slot =>
|
|
|
|
withCtx((props: any) => {
|
2020-03-19 06:14:51 +08:00
|
|
|
if (__DEV__ && currentInstance) {
|
2020-03-17 00:20:52 +08:00
|
|
|
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))
|
2021-03-06 01:17:27 +08:00
|
|
|
}, ctx) as Slot
|
2019-05-31 18:07:43 +08:00
|
|
|
|
2020-04-07 09:06:48 +08:00
|
|
|
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 = (
|
2019-09-07 00:58:31 +08:00
|
|
|
instance: ComponentInternalInstance,
|
2020-01-29 11:58:02 +08:00
|
|
|
children: VNodeNormalizedChildren
|
2020-04-07 09:06:48 +08:00
|
|
|
) => {
|
|
|
|
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
|
|
|
|
) => {
|
2019-08-22 23:12:37 +08:00
|
|
|
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
|
2020-07-14 00:36:41 +08:00
|
|
|
const type = (children as RawSlots)._
|
|
|
|
if (type) {
|
2020-06-30 21:26:25 +08:00
|
|
|
instance.slots = children as InternalSlots
|
|
|
|
// make compiler marker non-enumerable
|
2020-07-14 00:36:41 +08:00
|
|
|
def(children as InternalSlots, '_', type)
|
2019-05-31 18:07:43 +08:00
|
|
|
} else {
|
2020-04-07 09:06:48 +08:00
|
|
|
normalizeObjectSlots(children as RawSlots, (instance.slots = {}))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
instance.slots = {}
|
|
|
|
if (children) {
|
|
|
|
normalizeVNodeSlots(instance, children)
|
|
|
|
}
|
|
|
|
}
|
2020-04-16 04:45:20 +08:00
|
|
|
def(instance.slots, InternalObjectKey, 1)
|
2020-04-07 09:06:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export const updateSlots = (
|
|
|
|
instance: ComponentInternalInstance,
|
2021-04-02 07:28:58 +08:00
|
|
|
children: VNodeNormalizedChildren,
|
|
|
|
optimized: boolean
|
2020-04-07 09:06:48 +08:00
|
|
|
) => {
|
|
|
|
const { vnode, slots } = instance
|
|
|
|
let needDeletionCheck = true
|
|
|
|
let deletionComparisonTarget = EMPTY_OBJ
|
|
|
|
if (vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
|
2020-07-14 00:36:41 +08:00
|
|
|
const type = (children as RawSlots)._
|
|
|
|
if (type) {
|
2020-04-09 00:23:44 +08:00
|
|
|
// compiled slots.
|
2020-06-13 02:53:48 +08:00
|
|
|
if (__DEV__ && isHmrUpdating) {
|
2020-05-29 22:50:01 +08:00
|
|
|
// Parent was HMR updated so slot content may have changed.
|
|
|
|
// force update slots and mark instance for hmr as well
|
2020-06-30 23:43:15 +08:00
|
|
|
extend(slots, children as Slots)
|
2021-04-02 07:28:58 +08:00
|
|
|
} else if (optimized && type === SlotFlags.STABLE) {
|
2020-07-16 08:12:49 +08:00
|
|
|
// compiled AND stable.
|
2020-04-09 00:23:44 +08:00
|
|
|
// no need to update, and skip stale slots removal.
|
2020-04-07 09:06:48 +08:00
|
|
|
needDeletionCheck = false
|
2020-04-09 00:23:44 +08:00
|
|
|
} else {
|
2020-07-14 00:36:41 +08:00
|
|
|
// compiled but dynamic (v-if/v-for on slots) - update slots, but skip
|
|
|
|
// normalization.
|
2020-04-07 09:06:48 +08:00
|
|
|
extend(slots, children as Slots)
|
2021-04-02 07:28:58 +08:00
|
|
|
// #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._
|
|
|
|
}
|
2019-05-31 18:07:43 +08:00
|
|
|
}
|
2020-04-07 09:06:48 +08:00
|
|
|
} else {
|
|
|
|
needDeletionCheck = !(children as RawSlots).$stable
|
|
|
|
normalizeObjectSlots(children as RawSlots, slots)
|
2019-05-31 18:07:43 +08:00
|
|
|
}
|
2020-04-07 09:06:48 +08:00
|
|
|
deletionComparisonTarget = children as RawSlots
|
2020-03-19 06:14:51 +08:00
|
|
|
} else if (children) {
|
2019-06-03 19:58:12 +08:00
|
|
|
// non slot object children (direct value) passed to a component
|
2020-04-07 09:06:48 +08:00
|
|
|
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]
|
|
|
|
}
|
2019-05-31 18:07:43 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|