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