4
4
effectScope ,
5
5
isReactive ,
6
6
shallowReactive ,
7
+ shallowRef ,
7
8
} from '@vue/reactivity'
8
9
import {
9
10
type ComponentInternalInstance ,
@@ -12,7 +13,13 @@ import {
12
13
} from './component'
13
14
import { type Block , type Fragment , fragmentKey } from './apiRender'
14
15
import { firstEffect , renderEffect } from './renderEffect'
15
- import { createComment , createTextNode , insert , remove } from './dom/element'
16
+ import {
17
+ createComment ,
18
+ createTextNode ,
19
+ insert ,
20
+ normalizeBlock ,
21
+ remove ,
22
+ } from './dom/element'
16
23
import type { NormalizedRawProps } from './componentProps'
17
24
import type { Data } from '@vue/runtime-shared'
18
25
import { mergeProps } from './dom/prop'
@@ -107,27 +114,30 @@ export function initSlots(
107
114
export function createSlot (
108
115
name : string | ( ( ) => string ) ,
109
116
binds ?: NormalizedRawProps ,
110
- fallback ?: ( ) => Block ,
117
+ fallback ?: Slot ,
111
118
) : Block {
112
- let block : Block | undefined
113
- let branch : Slot | undefined
114
- let oldBranch : Slot | undefined
115
- let parent : ParentNode | undefined | null
116
- let scope : EffectScope | undefined
117
- const isDynamicName = isFunction ( name )
118
- const instance = currentInstance !
119
- const { slots } = instance
119
+ const { slots } = currentInstance !
120
+
121
+ const slotBlock = shallowRef < Block > ( )
122
+ let slotBranch : Slot | undefined
123
+ let slotScope : EffectScope | undefined
124
+
125
+ let fallbackBlock : Block | undefined
126
+ let fallbackBranch : Slot | undefined
127
+ let fallbackScope : EffectScope | undefined
120
128
121
- // When not using dynamic slots, simplify the process to improve performance
122
- if ( ! isDynamicName && ! isReactive ( slots ) ) {
123
- if ( ( branch = withProps ( slots [ name ] ) || fallback ) ) {
124
- return branch ( binds )
129
+ const normalizeBinds = binds && normalizeSlotProps ( binds )
130
+
131
+ const isDynamicName = isFunction ( name )
132
+ // fast path for static slots & without fallback
133
+ if ( ! isDynamicName && ! isReactive ( slots ) && ! fallback ) {
134
+ if ( ( slotBranch = slots [ name ] ) ) {
135
+ return slotBranch ( normalizeBinds )
125
136
} else {
126
137
return [ ]
127
138
}
128
139
}
129
140
130
- const getSlot = isDynamicName ? ( ) => slots [ name ( ) ] : ( ) => slots [ name ]
131
141
const anchor = __DEV__ ? createComment ( 'slot' ) : createTextNode ( )
132
142
const fragment : Fragment = {
133
143
nodes : [ ] ,
@@ -137,29 +147,76 @@ export function createSlot(
137
147
138
148
// TODO lifecycle hooks
139
149
renderEffect ( ( ) => {
140
- if ( ( branch = withProps ( getSlot ( ) ) || fallback ) !== oldBranch ) {
141
- parent ||= anchor . parentNode
142
- if ( block ) {
143
- scope ! . stop ( )
144
- remove ( block , parent ! )
150
+ const parent = anchor . parentNode
151
+
152
+ if (
153
+ ! slotBlock . value || // not initied
154
+ fallbackScope || // in fallback slot
155
+ isValidBlock ( slotBlock . value ) // slot block is valid
156
+ ) {
157
+ renderSlot ( parent )
158
+ } else {
159
+ renderFallback ( parent )
160
+ }
161
+ } )
162
+
163
+ return fragment
164
+
165
+ function renderSlot ( parent : ParentNode | null ) {
166
+ // from fallback to slot
167
+ const fromFallback = fallbackScope
168
+ if ( fromFallback ) {
169
+ // clean fallback slot
170
+ fallbackScope ! . stop ( )
171
+ remove ( fallbackBlock ! , parent ! )
172
+ fallbackScope = fallbackBlock = undefined
173
+ }
174
+
175
+ const slotName = isFunction ( name ) ? name ( ) : name
176
+ const branch = slots [ slotName ] !
177
+
178
+ if ( branch ) {
179
+ // init slot scope and block or switch branch
180
+ if ( ! slotScope || slotBranch !== branch ) {
181
+ // clean previous slot
182
+ if ( slotScope && ! fromFallback ) {
183
+ slotScope . stop ( )
184
+ remove ( slotBlock . value ! , parent ! )
185
+ }
186
+
187
+ slotBranch = branch
188
+ slotScope = effectScope ( )
189
+ slotBlock . value = slotScope . run ( ( ) => slotBranch ! ( normalizeBinds ) )
145
190
}
146
- if ( ( oldBranch = branch ) ) {
147
- scope = effectScope ( )
148
- fragment . nodes = block = scope . run ( ( ) => branch ! ( binds ) ) !
149
- parent && insert ( block , parent , anchor )
191
+
192
+ // if slot block is valid, render it
193
+ if ( slotBlock . value && isValidBlock ( slotBlock . value ) ) {
194
+ fragment . nodes = slotBlock . value
195
+ parent && insert ( slotBlock . value , parent , anchor )
150
196
} else {
151
- scope = block = undefined
152
- fragment . nodes = [ ]
197
+ renderFallback ( parent )
153
198
}
199
+ } else {
200
+ renderFallback ( parent )
154
201
}
155
- } )
202
+ }
156
203
157
- return fragment
204
+ function renderFallback ( parent : ParentNode | null ) {
205
+ // if slot branch is initied, remove it from DOM, but keep the scope
206
+ if ( slotBranch ) {
207
+ remove ( slotBlock . value ! , parent ! )
208
+ }
158
209
159
- function withProps < T extends ( p : any ) => any > ( fn ?: T ) {
160
- if ( fn )
161
- return ( binds ?: NormalizedRawProps ) : ReturnType < T > =>
162
- fn ( binds && normalizeSlotProps ( binds ) )
210
+ fallbackBranch ||= fallback
211
+ if ( fallbackBranch ) {
212
+ fallbackScope = effectScope ( )
213
+ fragment . nodes = fallbackBlock = fallbackScope . run ( ( ) =>
214
+ fallbackBranch ! ( normalizeBinds ) ,
215
+ ) !
216
+ parent && insert ( fallbackBlock , parent , anchor )
217
+ } else {
218
+ fragment . nodes = [ ]
219
+ }
163
220
}
164
221
}
165
222
@@ -214,3 +271,9 @@ function normalizeSlotProps(rawPropsList: NormalizedRawProps) {
214
271
}
215
272
}
216
273
}
274
+
275
+ function isValidBlock ( block : Block ) {
276
+ return (
277
+ normalizeBlock ( block ) . filter ( node => ! ( node instanceof Comment ) ) . length > 0
278
+ )
279
+ }
0 commit comments