99 shallowRef ,
1010 toReactive ,
1111} from '@vue/reactivity'
12- import { getSequence , isArray , isObject , isString } from '@vue/shared'
12+ import { isArray , isObject , isString } from '@vue/shared'
1313import { createComment , createTextNode } from './dom/node'
1414import {
1515 type Block ,
@@ -132,149 +132,173 @@ export const createFor = (
132132 unmount ( oldBlocks [ i ] )
133133 }
134134 } 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 )
147162 }
163+ break
148164 }
149165
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 ] )
157173 } else {
158- break
174+ queuedBlocks [ queuedBlocksInsertIndex ++ ] = [
175+ startOffset ,
176+ currentItem ,
177+ currentKey ,
178+ ]
179+ previousKeyIndexPairs [ previousKeyIndexInsertIndex ++ ] = [
180+ previousKey ,
181+ startOffset ,
182+ ]
159183 }
184+ startOffset ++
160185 }
161186
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+ ]
181192 }
182193
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 ]
195202 }
196203
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 )
209209 }
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 )
231241 } 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+ ] )
248248 }
249249 }
250250
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 )
276283 }
277284 }
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+ }
278302 }
279303 }
280304 }
@@ -294,13 +318,15 @@ export const createFor = (
294318 source : ResolvedSource ,
295319 idx : number ,
296320 anchor : Node | undefined = parentAnchor ,
321+ [ item , key , index ] = getItem ( source , idx ) ,
322+ key2 = getKey && getKey ( item , key , index ) ,
297323 ) : ForBlock => {
298- const [ item , key , index ] = getItem ( source , idx )
299324 const itemRef = shallowRef ( item )
300325 // avoid creating refs if the render fn doesn't need it
301326 const keyRef = needKey ? shallowRef ( key ) : undefined
302327 const indexRef = needIndex ? shallowRef ( index ) : undefined
303328
329+ currentKey = key2
304330 let nodes : Block
305331 let scope : EffectScope | undefined
306332 if ( isComponent ) {
@@ -319,23 +345,14 @@ export const createFor = (
319345 itemRef ,
320346 keyRef ,
321347 indexRef ,
322- getKey && getKey ( item , key , index ) ,
348+ key2 ,
323349 ) )
324350
325351 if ( parent ) insert ( block . nodes , parent , anchor )
326352
327353 return block
328354 }
329355
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-
339356 const update = (
340357 { itemRef, keyRef, indexRef } : ForBlock ,
341358 newItem : any ,
0 commit comments