|
|
@@ -3,9 +3,18 @@ import {
|
|
|
VNode,
|
|
|
VNodeNormalizedChildren,
|
|
|
normalizeVNode,
|
|
|
- VNodeChild
|
|
|
+ VNodeChild,
|
|
|
+ InternalObjectSymbol
|
|
|
} from './vnode'
|
|
|
-import { isArray, isFunction, EMPTY_OBJ, ShapeFlags } from '@vue/shared'
|
|
|
+import {
|
|
|
+ isArray,
|
|
|
+ isFunction,
|
|
|
+ EMPTY_OBJ,
|
|
|
+ ShapeFlags,
|
|
|
+ PatchFlags,
|
|
|
+ extend,
|
|
|
+ def
|
|
|
+} from '@vue/shared'
|
|
|
import { warn } from './warning'
|
|
|
import { isKeepAlive } from './components/KeepAlive'
|
|
|
import { withCtx } from './helpers/withRenderContext'
|
|
|
@@ -25,10 +34,12 @@ export type RawSlots = {
|
|
|
// internal, for tracking slot owner instance. This is attached during
|
|
|
// normalizeChildren when the component vnode is created.
|
|
|
_ctx?: ComponentInternalInstance | null
|
|
|
- // internal, indicates compiler generated slots = can skip normalization
|
|
|
+ // internal, indicates compiler generated slots
|
|
|
_?: 1
|
|
|
}
|
|
|
|
|
|
+const isInternalKey = (key: string) => key[0] === '_' || key === '$stable'
|
|
|
+
|
|
|
const normalizeSlotValue = (value: unknown): VNode[] =>
|
|
|
isArray(value)
|
|
|
? value.map(normalizeVNode)
|
|
|
@@ -50,46 +61,94 @@ const normalizeSlot = (
|
|
|
return normalizeSlotValue(rawSlot(props))
|
|
|
}, ctx)
|
|
|
|
|
|
-export function resolveSlots(
|
|
|
+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
|
|
|
-) {
|
|
|
- let slots: InternalSlots | void
|
|
|
+) => {
|
|
|
+ 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 rawSlots = children as RawSlots
|
|
|
- if (rawSlots._ === 1) {
|
|
|
- // pre-normalized slots object generated by compiler
|
|
|
- slots = children as Slots
|
|
|
+ if ((children as RawSlots)._ === 1) {
|
|
|
+ instance.slots = children as InternalSlots
|
|
|
} else {
|
|
|
- slots = {}
|
|
|
- const ctx = rawSlots._ctx
|
|
|
- for (const key in rawSlots) {
|
|
|
- if (key === '$stable' || key === '_ctx') 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
|
|
|
- }
|
|
|
+ normalizeObjectSlots(children as RawSlots, (instance.slots = {}))
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ instance.slots = {}
|
|
|
+ if (children) {
|
|
|
+ normalizeVNodeSlots(instance, children)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ def(instance.slots, InternalObjectSymbol, true)
|
|
|
+}
|
|
|
+
|
|
|
+export const updateSlots = (
|
|
|
+ instance: ComponentInternalInstance,
|
|
|
+ children: VNodeNormalizedChildren
|
|
|
+) => {
|
|
|
+ const { vnode, slots } = instance
|
|
|
+ let needDeletionCheck = true
|
|
|
+ let deletionComparisonTarget = EMPTY_OBJ
|
|
|
+ if (vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
|
|
|
+ if ((children as RawSlots)._ === 1) {
|
|
|
+ if (!(vnode.patchFlag & PatchFlags.DYNAMIC_SLOTS)) {
|
|
|
+ // compiled AND static. this means we can skip removal of potential
|
|
|
+ // stale slots
|
|
|
+ needDeletionCheck = false
|
|
|
+ }
|
|
|
+ // HMR force update
|
|
|
+ if (__DEV__ && instance.parent && instance.parent.renderUpdated) {
|
|
|
+ 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
|
|
|
- if (__DEV__ && !isKeepAlive(instance.vnode)) {
|
|
|
- warn(
|
|
|
- `Non-function value encountered for default slot. ` +
|
|
|
- `Prefer function slots for better performance.`
|
|
|
- )
|
|
|
+ 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]
|
|
|
+ }
|
|
|
}
|
|
|
- const normalized = normalizeSlotValue(children)
|
|
|
- slots = { default: () => normalized }
|
|
|
}
|
|
|
- instance.slots = slots || EMPTY_OBJ
|
|
|
}
|