@@ -10,7 +10,7 @@ import {
1010 toReactive ,
1111 watch ,
1212} from '@vue/reactivity'
13- import { getSequence , isArray , isObject , isString } from '@vue/shared'
13+ import { isArray , isObject , isString } from '@vue/shared'
1414import { createComment , createTextNode } from './dom/node'
1515import {
1616 type Block ,
@@ -142,149 +142,173 @@ export const createFor = (
142142 unmount ( oldBlocks [ i ] )
143143 }
144144 } 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 )
157172 }
173+ break
158174 }
159175
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 ] )
167183 } else {
168- break
184+ queuedBlocks [ queuedBlocksInsertIndex ++ ] = [
185+ startOffset ,
186+ currentItem ,
187+ currentKey ,
188+ ]
189+ previousKeyIndexPairs [ previousKeyIndexInsertIndex ++ ] = [
190+ previousKey ,
191+ startOffset ,
192+ ]
169193 }
194+ startOffset ++
170195 }
171196
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+ ]
191202 }
192203
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 ]
205212 }
206213
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 )
219219 }
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 )
241251 } 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+ ] )
258258 }
259259 }
260260
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 )
286293 }
287294 }
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+ }
288312 }
289313 }
290314 }
@@ -304,13 +328,15 @@ export const createFor = (
304328 source : ResolvedSource ,
305329 idx : number ,
306330 anchor : Node | undefined = parentAnchor ,
331+ [ item , key , index ] = getItem ( source , idx ) ,
332+ key2 = getKey && getKey ( item , key , index ) ,
307333 ) : ForBlock => {
308- const [ item , key , index ] = getItem ( source , idx )
309334 const itemRef = shallowRef ( item )
310335 // avoid creating refs if the render fn doesn't need it
311336 const keyRef = needKey ? shallowRef ( key ) : undefined
312337 const indexRef = needIndex ? shallowRef ( index ) : undefined
313338
339+ currentKey = key2
314340 let nodes : Block
315341 let scope : EffectScope | undefined
316342 if ( isComponent ) {
@@ -329,23 +355,14 @@ export const createFor = (
329355 itemRef ,
330356 keyRef ,
331357 indexRef ,
332- getKey && getKey ( item , key , index ) ,
358+ key2 ,
333359 ) )
334360
335361 if ( parent ) insert ( block . nodes , parent , anchor )
336362
337363 return block
338364 }
339365
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-
349366 const update = (
350367 { itemRef, keyRef, indexRef } : ForBlock ,
351368 newItem : any ,
0 commit comments