@@ -11,7 +11,7 @@ import {
1111 toReadonly ,
1212 watch ,
1313} from '@vue/reactivity'
14- import { getSequence , isArray , isObject , isString } from '@vue/shared'
14+ import { isArray , isObject , isString } from '@vue/shared'
1515import { createComment , createTextNode } from './dom/node'
1616import {
1717 type Block ,
@@ -150,149 +150,173 @@ export const createFor = (
150150 unmount ( oldBlocks [ i ] )
151151 }
152152 } 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 )
165180 }
181+ break
166182 }
167183
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 ] )
175191 } else {
176- break
192+ queuedBlocks [ queuedBlocksInsertIndex ++ ] = [
193+ startOffset ,
194+ currentItem ,
195+ currentKey ,
196+ ]
197+ previousKeyIndexPairs [ previousKeyIndexInsertIndex ++ ] = [
198+ previousKey ,
199+ startOffset ,
200+ ]
177201 }
202+ startOffset ++
178203 }
179204
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+ ]
199210 }
200211
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 ]
213220 }
214221
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 )
227227 }
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 )
249259 } 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+ ] )
266266 }
267267 }
268268
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 )
294301 }
295302 }
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+ }
296320 }
297321 }
298322 }
@@ -312,13 +336,15 @@ export const createFor = (
312336 source : ResolvedSource ,
313337 idx : number ,
314338 anchor : Node | undefined = parentAnchor ,
339+ [ item , key , index ] = getItem ( source , idx ) ,
340+ key2 = getKey && getKey ( item , key , index ) ,
315341 ) : ForBlock => {
316- const [ item , key , index ] = getItem ( source , idx )
317342 const itemRef = shallowRef ( item )
318343 // avoid creating refs if the render fn doesn't need it
319344 const keyRef = needKey ? shallowRef ( key ) : undefined
320345 const indexRef = needIndex ? shallowRef ( index ) : undefined
321346
347+ currentKey = key2
322348 let nodes : Block
323349 let scope : EffectScope | undefined
324350 if ( isComponent ) {
@@ -337,23 +363,14 @@ export const createFor = (
337363 itemRef ,
338364 keyRef ,
339365 indexRef ,
340- getKey && getKey ( item , key , index ) ,
366+ key2 ,
341367 ) )
342368
343369 if ( parent ) insert ( block . nodes , parent , anchor )
344370
345371 return block
346372 }
347373
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-
357374 const update = (
358375 { itemRef, keyRef, indexRef } : ForBlock ,
359376 newItem : any ,
0 commit comments