|
|
@@ -4,6 +4,7 @@ import {
|
|
|
effectScope,
|
|
|
isReactive,
|
|
|
shallowReactive,
|
|
|
+ shallowRef,
|
|
|
} from '@vue/reactivity'
|
|
|
import {
|
|
|
type ComponentInternalInstance,
|
|
|
@@ -12,7 +13,13 @@ import {
|
|
|
} from './component'
|
|
|
import { type Block, type Fragment, fragmentKey } from './apiRender'
|
|
|
import { firstEffect, renderEffect } from './renderEffect'
|
|
|
-import { createComment, createTextNode, insert, remove } from './dom/element'
|
|
|
+import {
|
|
|
+ createComment,
|
|
|
+ createTextNode,
|
|
|
+ insert,
|
|
|
+ normalizeBlock,
|
|
|
+ remove,
|
|
|
+} from './dom/element'
|
|
|
import type { NormalizedRawProps } from './componentProps'
|
|
|
import type { Data } from '@vue/runtime-shared'
|
|
|
import { mergeProps } from './dom/prop'
|
|
|
@@ -107,27 +114,30 @@ export function initSlots(
|
|
|
export function createSlot(
|
|
|
name: string | (() => string),
|
|
|
binds?: NormalizedRawProps,
|
|
|
- fallback?: () => Block,
|
|
|
+ fallback?: Slot,
|
|
|
): Block {
|
|
|
- let block: Block | undefined
|
|
|
- let branch: Slot | undefined
|
|
|
- let oldBranch: Slot | undefined
|
|
|
- let parent: ParentNode | undefined | null
|
|
|
- let scope: EffectScope | undefined
|
|
|
- const isDynamicName = isFunction(name)
|
|
|
- const instance = currentInstance!
|
|
|
- const { slots } = instance
|
|
|
+ const { slots } = currentInstance!
|
|
|
+
|
|
|
+ const slotBlock = shallowRef<Block>()
|
|
|
+ let slotBranch: Slot | undefined
|
|
|
+ let slotScope: EffectScope | undefined
|
|
|
+
|
|
|
+ let fallbackBlock: Block | undefined
|
|
|
+ let fallbackBranch: Slot | undefined
|
|
|
+ let fallbackScope: EffectScope | undefined
|
|
|
|
|
|
- // When not using dynamic slots, simplify the process to improve performance
|
|
|
- if (!isDynamicName && !isReactive(slots)) {
|
|
|
- if ((branch = withProps(slots[name]) || fallback)) {
|
|
|
- return branch(binds)
|
|
|
+ const normalizeBinds = binds && normalizeSlotProps(binds)
|
|
|
+
|
|
|
+ const isDynamicName = isFunction(name)
|
|
|
+ // fast path for static slots & without fallback
|
|
|
+ if (!isDynamicName && !isReactive(slots) && !fallback) {
|
|
|
+ if ((slotBranch = slots[name])) {
|
|
|
+ return slotBranch(normalizeBinds)
|
|
|
} else {
|
|
|
return []
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- const getSlot = isDynamicName ? () => slots[name()] : () => slots[name]
|
|
|
const anchor = __DEV__ ? createComment('slot') : createTextNode()
|
|
|
const fragment: Fragment = {
|
|
|
nodes: [],
|
|
|
@@ -137,29 +147,76 @@ export function createSlot(
|
|
|
|
|
|
// TODO lifecycle hooks
|
|
|
renderEffect(() => {
|
|
|
- if ((branch = withProps(getSlot()) || fallback) !== oldBranch) {
|
|
|
- parent ||= anchor.parentNode
|
|
|
- if (block) {
|
|
|
- scope!.stop()
|
|
|
- remove(block, parent!)
|
|
|
+ const parent = anchor.parentNode
|
|
|
+
|
|
|
+ if (
|
|
|
+ !slotBlock.value || // not initied
|
|
|
+ fallbackScope || // in fallback slot
|
|
|
+ isValidBlock(slotBlock.value) // slot block is valid
|
|
|
+ ) {
|
|
|
+ renderSlot(parent)
|
|
|
+ } else {
|
|
|
+ renderFallback(parent)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ return fragment
|
|
|
+
|
|
|
+ function renderSlot(parent: ParentNode | null) {
|
|
|
+ // from fallback to slot
|
|
|
+ const fromFallback = fallbackScope
|
|
|
+ if (fromFallback) {
|
|
|
+ // clean fallback slot
|
|
|
+ fallbackScope!.stop()
|
|
|
+ remove(fallbackBlock!, parent!)
|
|
|
+ fallbackScope = fallbackBlock = undefined
|
|
|
+ }
|
|
|
+
|
|
|
+ const slotName = isFunction(name) ? name() : name
|
|
|
+ const branch = slots[slotName]!
|
|
|
+
|
|
|
+ if (branch) {
|
|
|
+ // init slot scope and block or switch branch
|
|
|
+ if (!slotScope || slotBranch !== branch) {
|
|
|
+ // clean previous slot
|
|
|
+ if (slotScope && !fromFallback) {
|
|
|
+ slotScope.stop()
|
|
|
+ remove(slotBlock.value!, parent!)
|
|
|
+ }
|
|
|
+
|
|
|
+ slotBranch = branch
|
|
|
+ slotScope = effectScope()
|
|
|
+ slotBlock.value = slotScope.run(() => slotBranch!(normalizeBinds))
|
|
|
}
|
|
|
- if ((oldBranch = branch)) {
|
|
|
- scope = effectScope()
|
|
|
- fragment.nodes = block = scope.run(() => branch!(binds))!
|
|
|
- parent && insert(block, parent, anchor)
|
|
|
+
|
|
|
+ // if slot block is valid, render it
|
|
|
+ if (slotBlock.value && isValidBlock(slotBlock.value)) {
|
|
|
+ fragment.nodes = slotBlock.value
|
|
|
+ parent && insert(slotBlock.value, parent, anchor)
|
|
|
} else {
|
|
|
- scope = block = undefined
|
|
|
- fragment.nodes = []
|
|
|
+ renderFallback(parent)
|
|
|
}
|
|
|
+ } else {
|
|
|
+ renderFallback(parent)
|
|
|
}
|
|
|
- })
|
|
|
+ }
|
|
|
|
|
|
- return fragment
|
|
|
+ function renderFallback(parent: ParentNode | null) {
|
|
|
+ // if slot branch is initied, remove it from DOM, but keep the scope
|
|
|
+ if (slotBranch) {
|
|
|
+ remove(slotBlock.value!, parent!)
|
|
|
+ }
|
|
|
|
|
|
- function withProps<T extends (p: any) => any>(fn?: T) {
|
|
|
- if (fn)
|
|
|
- return (binds?: NormalizedRawProps): ReturnType<T> =>
|
|
|
- fn(binds && normalizeSlotProps(binds))
|
|
|
+ fallbackBranch ||= fallback
|
|
|
+ if (fallbackBranch) {
|
|
|
+ fallbackScope = effectScope()
|
|
|
+ fragment.nodes = fallbackBlock = fallbackScope.run(() =>
|
|
|
+ fallbackBranch!(normalizeBinds),
|
|
|
+ )!
|
|
|
+ parent && insert(fallbackBlock, parent, anchor)
|
|
|
+ } else {
|
|
|
+ fragment.nodes = []
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -214,3 +271,9 @@ function normalizeSlotProps(rawPropsList: NormalizedRawProps) {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+function isValidBlock(block: Block) {
|
|
|
+ return (
|
|
|
+ normalizeBlock(block).filter(node => !(node instanceof Comment)).length > 0
|
|
|
+ )
|
|
|
+}
|