9
9
shallowRef ,
10
10
toReactive ,
11
11
} from '@vue/reactivity'
12
- import { getSequence , isArray , isObject , isString } from '@vue/shared'
12
+ import { isArray , isObject , isString } from '@vue/shared'
13
13
import { createComment , createTextNode } from './dom/node'
14
14
import {
15
15
type Block ,
@@ -132,149 +132,173 @@ export const createFor = (
132
132
unmount ( oldBlocks [ i ] )
133
133
}
134
134
} else {
135
- let i = 0
136
- let e1 = oldLength - 1 // prev ending index
137
- let e2 = newLength - 1 // next ending index
138
-
139
- // 1. sync from start
140
- // (a b) c
141
- // (a b) d e
142
- while ( i <= e1 && i <= e2 ) {
143
- if ( tryPatchIndex ( source , i ) ) {
144
- i ++
145
- } else {
146
- break
135
+ const sharedBlockCount = Math . min ( oldLength , newLength )
136
+ const previousKeyIndexPairs : [ any , number ] [ ] = new Array ( oldLength )
137
+ const queuedBlocks : [
138
+ blockIndex : number ,
139
+ blockItem : ReturnType < typeof getItem > ,
140
+ blockKey : any ,
141
+ ] [ ] = new Array ( newLength )
142
+
143
+ let anchorFallback : Node = parentAnchor
144
+ let endOffset = 0
145
+ let startOffset = 0
146
+ let queuedBlocksInsertIndex = 0
147
+ let previousKeyIndexInsertIndex = 0
148
+
149
+ while ( endOffset < sharedBlockCount ) {
150
+ const currentIndex = newLength - endOffset - 1
151
+ const currentItem = getItem ( source , currentIndex )
152
+ const currentKey = getKey ( ...currentItem )
153
+ const existingBlock = oldBlocks [ oldLength - endOffset - 1 ]
154
+ if ( existingBlock . key === currentKey ) {
155
+ update ( existingBlock , ...currentItem )
156
+ newBlocks [ currentIndex ] = existingBlock
157
+ endOffset ++
158
+ continue
159
+ }
160
+ if ( endOffset !== 0 ) {
161
+ anchorFallback = normalizeAnchor ( newBlocks [ currentIndex + 1 ] . nodes )
147
162
}
163
+ break
148
164
}
149
165
150
- // 2. sync from end
151
- // a (b c )
152
- // d e (b c )
153
- while ( i <= e1 && i <= e2 ) {
154
- if ( tryPatchIndex ( source , i ) ) {
155
- e1 --
156
- e2 --
166
+ while ( startOffset < sharedBlockCount - endOffset ) {
167
+ const currentItem = getItem ( source , startOffset )
168
+ const currentKey = getKey ( ... currentItem )
169
+ const previousBlock = oldBlocks [ startOffset ]
170
+ const previousKey = previousBlock . key
171
+ if ( previousKey === currentKey ) {
172
+ update ( ( newBlocks [ startOffset ] = previousBlock ) , currentItem [ 0 ] )
157
173
} else {
158
- break
174
+ queuedBlocks [ queuedBlocksInsertIndex ++ ] = [
175
+ startOffset ,
176
+ currentItem ,
177
+ currentKey ,
178
+ ]
179
+ previousKeyIndexPairs [ previousKeyIndexInsertIndex ++ ] = [
180
+ previousKey ,
181
+ startOffset ,
182
+ ]
159
183
}
184
+ startOffset ++
160
185
}
161
186
162
- // 3. common sequence + mount
163
- // (a b)
164
- // (a b) c
165
- // i = 2, e1 = 1, e2 = 2
166
- // (a b)
167
- // c (a b)
168
- // i = 0, e1 = -1, e2 = 0
169
- if ( i > e1 ) {
170
- if ( i <= e2 ) {
171
- const nextPos = e2 + 1
172
- const anchor =
173
- nextPos < newLength
174
- ? normalizeAnchor ( newBlocks [ nextPos ] . nodes )
175
- : parentAnchor
176
- while ( i <= e2 ) {
177
- mount ( source , i , anchor )
178
- i ++
179
- }
180
- }
187
+ for ( let i = startOffset ; i < oldLength - endOffset ; i ++ ) {
188
+ previousKeyIndexPairs [ previousKeyIndexInsertIndex ++ ] = [
189
+ oldBlocks [ i ] . key ,
190
+ i ,
191
+ ]
181
192
}
182
193
183
- // 4. common sequence + unmount
184
- // (a b) c
185
- // (a b)
186
- // i = 2, e1 = 2, e2 = 1
187
- // a (b c)
188
- // (b c)
189
- // i = 0, e1 = 0, e2 = -1
190
- else if ( i > e2 ) {
191
- while ( i <= e1 ) {
192
- unmount ( oldBlocks [ i ] )
193
- i ++
194
- }
194
+ const preparationBlockCount = Math . min (
195
+ newLength - endOffset ,
196
+ sharedBlockCount ,
197
+ )
198
+ for ( let i = startOffset ; i < preparationBlockCount ; i ++ ) {
199
+ const blockItem = getItem ( source , i )
200
+ const blockKey = getKey ( ...blockItem )
201
+ queuedBlocks [ queuedBlocksInsertIndex ++ ] = [ i , blockItem , blockKey ]
195
202
}
196
203
197
- // 5. unknown sequence
198
- // [i ... e1 + 1]: a b [c d e] f g
199
- // [i ... e2 + 1]: a b [e d c h] f g
200
- // i = 2, e1 = 4, e2 = 5
201
- else {
202
- const s1 = i // prev starting index
203
- const s2 = i // next starting index
204
-
205
- // 5.1 build key:index map for newChildren
206
- const keyToNewIndexMap = new Map ( )
207
- for ( i = s2 ; i <= e2 ; i ++ ) {
208
- keyToNewIndexMap . set ( getKey ( ...getItem ( source , i ) ) , i )
204
+ if ( ! queuedBlocksInsertIndex && ! previousKeyIndexInsertIndex ) {
205
+ for ( let i = preparationBlockCount ; i < newLength - endOffset ; i ++ ) {
206
+ const blockItem = getItem ( source , i )
207
+ const blockKey = getKey ( ...blockItem )
208
+ mount ( source , i , anchorFallback , blockItem , blockKey )
209
209
}
210
-
211
- // 5.2 loop through old children left to be patched and try to patch
212
- // matching nodes & remove nodes that are no longer present
213
- let j
214
- let patched = 0
215
- const toBePatched = e2 - s2 + 1
216
- let moved = false
217
- // used to track whether any node has moved
218
- let maxNewIndexSoFar = 0
219
- // works as Map<newIndex, oldIndex>
220
- // Note that oldIndex is offset by +1
221
- // and oldIndex = 0 is a special value indicating the new node has
222
- // no corresponding old node.
223
- // used for determining longest stable subsequence
224
- const newIndexToOldIndexMap = new Array ( toBePatched ) . fill ( 0 )
225
-
226
- for ( i = s1 ; i <= e1 ; i ++ ) {
227
- const prevBlock = oldBlocks [ i ]
228
- if ( patched >= toBePatched ) {
229
- // all new children have been patched so this can only be a removal
230
- unmount ( prevBlock )
210
+ } else {
211
+ queuedBlocks . length = queuedBlocksInsertIndex
212
+ previousKeyIndexPairs . length = previousKeyIndexInsertIndex
213
+
214
+ const previousKeyIndexMap = new Map ( previousKeyIndexPairs )
215
+ const blocksToMount : [
216
+ blockIndex : number ,
217
+ blockItem : ReturnType < typeof getItem > ,
218
+ blockKey : any ,
219
+ anchorOffset : number ,
220
+ ] [ ] = [ ]
221
+
222
+ const relocateOrMountBlock = (
223
+ blockIndex : number ,
224
+ blockItem : ReturnType < typeof getItem > ,
225
+ blockKey : any ,
226
+ anchorOffset : number ,
227
+ ) => {
228
+ const previousIndex = previousKeyIndexMap . get ( blockKey )
229
+ if ( previousIndex !== undefined ) {
230
+ const reusedBlock = ( newBlocks [ blockIndex ] =
231
+ oldBlocks [ previousIndex ] )
232
+ update ( reusedBlock , ...blockItem )
233
+ insert (
234
+ reusedBlock ,
235
+ parent ! ,
236
+ anchorOffset === - 1
237
+ ? anchorFallback
238
+ : normalizeAnchor ( newBlocks [ anchorOffset ] . nodes ) ,
239
+ )
240
+ previousKeyIndexMap . delete ( blockKey )
231
241
} else {
232
- const newIndex = keyToNewIndexMap . get ( prevBlock . key )
233
- if ( newIndex == null ) {
234
- unmount ( prevBlock )
235
- } else {
236
- newIndexToOldIndexMap [ newIndex - s2 ] = i + 1
237
- if ( newIndex >= maxNewIndexSoFar ) {
238
- maxNewIndexSoFar = newIndex
239
- } else {
240
- moved = true
241
- }
242
- update (
243
- ( newBlocks [ newIndex ] = prevBlock ) ,
244
- ...getItem ( source , newIndex ) ,
245
- )
246
- patched ++
247
- }
242
+ blocksToMount . push ( [
243
+ blockIndex ,
244
+ blockItem ,
245
+ blockKey ,
246
+ anchorOffset ,
247
+ ] )
248
248
}
249
249
}
250
250
251
- // 5.3 move and mount
252
- // generate longest stable subsequence only when nodes have moved
253
- const increasingNewIndexSequence = moved
254
- ? getSequence ( newIndexToOldIndexMap )
255
- : [ ]
256
- j = increasingNewIndexSequence . length - 1
257
- // looping backwards so that we can use last patched node as anchor
258
- for ( i = toBePatched - 1 ; i >= 0 ; i -- ) {
259
- const nextIndex = s2 + i
260
- const anchor =
261
- nextIndex + 1 < newLength
262
- ? normalizeAnchor ( newBlocks [ nextIndex + 1 ] . nodes )
263
- : parentAnchor
264
- if ( newIndexToOldIndexMap [ i ] === 0 ) {
265
- // mount new
266
- mount ( source , nextIndex , anchor )
267
- } else if ( moved ) {
268
- // move if:
269
- // There is no stable subsequence (e.g. a reverse)
270
- // OR current node is not among the stable sequence
271
- if ( j < 0 || i !== increasingNewIndexSequence [ j ] ) {
272
- insert ( newBlocks [ nextIndex ] . nodes , parent ! , anchor )
273
- } else {
274
- j --
275
- }
251
+ for ( let i = queuedBlocks . length - 1 ; i >= 0 ; i -- ) {
252
+ const [ blockIndex , blockItem , blockKey ] = queuedBlocks [ i ]
253
+ relocateOrMountBlock (
254
+ blockIndex ,
255
+ blockItem ,
256
+ blockKey ,
257
+ blockIndex < preparationBlockCount - 1 ? blockIndex + 1 : - 1 ,
258
+ )
259
+ }
260
+
261
+ for ( let i = preparationBlockCount ; i < newLength - endOffset ; i ++ ) {
262
+ const blockItem = getItem ( source , i )
263
+ const blockKey = getKey ( ...blockItem )
264
+ relocateOrMountBlock ( i , blockItem , blockKey , - 1 )
265
+ }
266
+
267
+ const useFastRemove = blocksToMount . length === newLength
268
+
269
+ for ( const leftoverIndex of previousKeyIndexMap . values ( ) ) {
270
+ unmount (
271
+ oldBlocks [ leftoverIndex ] ,
272
+ ! ( useFastRemove && canUseFastRemove ) ,
273
+ ! useFastRemove ,
274
+ )
275
+ }
276
+ if ( useFastRemove ) {
277
+ for ( const selector of selectors ) {
278
+ selector . cleanup ( )
279
+ }
280
+ if ( canUseFastRemove ) {
281
+ parent ! . textContent = ''
282
+ parent ! . appendChild ( parentAnchor )
276
283
}
277
284
}
285
+
286
+ for ( const [
287
+ blockIndex ,
288
+ blockItem ,
289
+ blockKey ,
290
+ anchorOffset ,
291
+ ] of blocksToMount ) {
292
+ mount (
293
+ source ,
294
+ blockIndex ,
295
+ anchorOffset === - 1
296
+ ? anchorFallback
297
+ : normalizeAnchor ( newBlocks [ anchorOffset ] . nodes ) ,
298
+ blockItem ,
299
+ blockKey ,
300
+ )
301
+ }
278
302
}
279
303
}
280
304
}
@@ -294,13 +318,15 @@ export const createFor = (
294
318
source : ResolvedSource ,
295
319
idx : number ,
296
320
anchor : Node | undefined = parentAnchor ,
321
+ [ item , key , index ] = getItem ( source , idx ) ,
322
+ key2 = getKey && getKey ( item , key , index ) ,
297
323
) : ForBlock => {
298
- const [ item , key , index ] = getItem ( source , idx )
299
324
const itemRef = shallowRef ( item )
300
325
// avoid creating refs if the render fn doesn't need it
301
326
const keyRef = needKey ? shallowRef ( key ) : undefined
302
327
const indexRef = needIndex ? shallowRef ( index ) : undefined
303
328
329
+ currentKey = key2
304
330
let nodes : Block
305
331
let scope : EffectScope | undefined
306
332
if ( isComponent ) {
@@ -319,23 +345,14 @@ export const createFor = (
319
345
itemRef ,
320
346
keyRef ,
321
347
indexRef ,
322
- getKey && getKey ( item , key , index ) ,
348
+ key2 ,
323
349
) )
324
350
325
351
if ( parent ) insert ( block . nodes , parent , anchor )
326
352
327
353
return block
328
354
}
329
355
330
- const tryPatchIndex = ( source : any , idx : number ) => {
331
- const block = oldBlocks [ idx ]
332
- const [ item , key , index ] = getItem ( source , idx )
333
- if ( block . key === getKey ! ( item , key , index ) ) {
334
- update ( ( newBlocks [ idx ] = block ) , item )
335
- return true
336
- }
337
- }
338
-
339
356
const update = (
340
357
{ itemRef, keyRef, indexRef } : ForBlock ,
341
358
newItem : any ,
0 commit comments