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