@@ -11,7 +11,7 @@ import {
11
11
toReactive ,
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 ,
@@ -134,149 +134,70 @@ export const createFor = (
134
134
unmount ( oldBlocks [ i ] )
135
135
}
136
136
} else {
137
- let i = 0
138
- let e1 = oldLength - 1 // prev ending index
139
- let e2 = newLength - 1 // next ending index
140
-
141
- // 1. sync from start
142
- // (a b) c
143
- // (a b) d e
144
- while ( i <= e1 && i <= e2 ) {
145
- if ( tryPatchIndex ( source , i ) ) {
146
- i ++
147
- } else {
148
- break
149
- }
150
- }
137
+ const commonLength = Math . min ( oldLength , newLength )
138
+ const unresolvedOlds = new Map < any , number > ( )
139
+ const unresolvedNews : [
140
+ index : number ,
141
+ item : ReturnType < typeof getItem > ,
142
+ key : any ,
143
+ ] [ ] = [ ]
151
144
152
- // 2. sync from end
153
- // a (b c )
154
- // d e (b c )
155
- while ( i <= e1 && i <= e2 ) {
156
- if ( tryPatchIndex ( source , i ) ) {
157
- e1 --
158
- e2 --
145
+ for ( let i = 0 ; i < commonLength ; i ++ ) {
146
+ const item = getItem ( source , i )
147
+ const key = getKey . apply ( null , item )
148
+ const oldBlock = oldBlocks [ i ]
149
+ const oldKey = oldBlock . key
150
+ if ( oldKey === key ) {
151
+ update ( ( newBlocks [ i ] = oldBlock ) , item [ 0 ] )
159
152
} else {
160
- break
153
+ unresolvedNews . push ( [ i , item , key ] )
154
+ unresolvedOlds . set ( oldKey , i )
161
155
}
162
156
}
163
157
164
- // 3. common sequence + mount
165
- // (a b)
166
- // (a b) c
167
- // i = 2, e1 = 1, e2 = 2
168
- // (a b)
169
- // c (a b)
170
- // i = 0, e1 = -1, e2 = 0
171
- if ( i > e1 ) {
172
- if ( i <= e2 ) {
173
- const nextPos = e2 + 1
174
- const anchor =
175
- nextPos < newLength
176
- ? normalizeAnchor ( newBlocks [ nextPos ] . nodes )
177
- : parentAnchor
178
- while ( i <= e2 ) {
179
- mount ( source , i , anchor )
180
- i ++
181
- }
182
- }
158
+ for ( let i = commonLength ; i < oldLength ; i ++ ) {
159
+ const oldBlock = oldBlocks [ i ]
160
+ unresolvedOlds . set ( oldBlock . key , i )
183
161
}
184
162
185
- // 4. common sequence + unmount
186
- // (a b) c
187
- // (a b)
188
- // i = 2, e1 = 2, e2 = 1
189
- // a (b c)
190
- // (b c)
191
- // i = 0, e1 = 0, e2 = -1
192
- else if ( i > e2 ) {
193
- while ( i <= e1 ) {
194
- unmount ( oldBlocks [ i ] )
195
- i ++
163
+ const unresolvedLength = unresolvedNews . length
164
+ const moveOrMount = (
165
+ index : number ,
166
+ item : ReturnType < typeof getItem > ,
167
+ key : any ,
168
+ anchor : Node ,
169
+ ) => {
170
+ const oldIndex = unresolvedOlds . get ( key )
171
+ if ( oldIndex !== undefined ) {
172
+ const block = ( newBlocks [ index ] = oldBlocks [ oldIndex ] )
173
+ update ( block , ...item )
174
+ insert ( block , parent ! , anchor )
175
+ unresolvedOlds . delete ( key )
176
+ } else {
177
+ mount ( source , index , item , key , anchor )
196
178
}
197
179
}
198
180
199
- // 5. unknown sequence
200
- // [i ... e1 + 1]: a b [c d e] f g
201
- // [i ... e2 + 1]: a b [e d c h] f g
202
- // i = 2, e1 = 4, e2 = 5
203
- else {
204
- const s1 = i // prev starting index
205
- const s2 = i // next starting index
206
-
207
- // 5.1 build key:index map for newChildren
208
- const keyToNewIndexMap = new Map ( )
209
- for ( i = s2 ; i <= e2 ; i ++ ) {
210
- keyToNewIndexMap . set ( getKey ( ...getItem ( source , i ) ) , i )
211
- }
181
+ for ( let i = unresolvedLength - 1 ; i >= 0 ; i -- ) {
182
+ const [ index , item , key ] = unresolvedNews [ i ]
183
+ moveOrMount (
184
+ index ,
185
+ item ,
186
+ key ,
187
+ index < commonLength - 1
188
+ ? normalizeAnchor ( newBlocks [ index + 1 ] . nodes )
189
+ : parentAnchor ,
190
+ )
191
+ }
212
192
213
- // 5.2 loop through old children left to be patched and try to patch
214
- // matching nodes & remove nodes that are no longer present
215
- let j
216
- let patched = 0
217
- const toBePatched = e2 - s2 + 1
218
- let moved = false
219
- // used to track whether any node has moved
220
- let maxNewIndexSoFar = 0
221
- // works as Map<newIndex, oldIndex>
222
- // Note that oldIndex is offset by +1
223
- // and oldIndex = 0 is a special value indicating the new node has
224
- // no corresponding old node.
225
- // used for determining longest stable subsequence
226
- const newIndexToOldIndexMap = new Array ( toBePatched ) . fill ( 0 )
227
-
228
- for ( i = s1 ; i <= e1 ; i ++ ) {
229
- const prevBlock = oldBlocks [ i ]
230
- if ( patched >= toBePatched ) {
231
- // all new children have been patched so this can only be a removal
232
- unmount ( prevBlock )
233
- } else {
234
- const newIndex = keyToNewIndexMap . get ( prevBlock . key )
235
- if ( newIndex == null ) {
236
- unmount ( prevBlock )
237
- } else {
238
- newIndexToOldIndexMap [ newIndex - s2 ] = i + 1
239
- if ( newIndex >= maxNewIndexSoFar ) {
240
- maxNewIndexSoFar = newIndex
241
- } else {
242
- moved = true
243
- }
244
- update (
245
- ( newBlocks [ newIndex ] = prevBlock ) ,
246
- ...getItem ( source , newIndex ) ,
247
- )
248
- patched ++
249
- }
250
- }
251
- }
193
+ for ( let index = commonLength ; index < newLength ; index ++ ) {
194
+ const item = getItem ( source , index )
195
+ const key = getKey . apply ( null , item )
196
+ moveOrMount ( index , item , key , parentAnchor )
197
+ }
252
198
253
- // 5.3 move and mount
254
- // generate longest stable subsequence only when nodes have moved
255
- const increasingNewIndexSequence = moved
256
- ? getSequence ( newIndexToOldIndexMap )
257
- : [ ]
258
- j = increasingNewIndexSequence . length - 1
259
- // looping backwards so that we can use last patched node as anchor
260
- for ( i = toBePatched - 1 ; i >= 0 ; i -- ) {
261
- const nextIndex = s2 + i
262
- const anchor =
263
- nextIndex + 1 < newLength
264
- ? normalizeAnchor ( newBlocks [ nextIndex + 1 ] . nodes )
265
- : parentAnchor
266
- if ( newIndexToOldIndexMap [ i ] === 0 ) {
267
- // mount new
268
- mount ( source , nextIndex , anchor )
269
- } else if ( moved ) {
270
- // move if:
271
- // There is no stable subsequence (e.g. a reverse)
272
- // OR current node is not among the stable sequence
273
- if ( j < 0 || i !== increasingNewIndexSequence [ j ] ) {
274
- insert ( newBlocks [ nextIndex ] . nodes , parent ! , anchor )
275
- } else {
276
- j --
277
- }
278
- }
279
- }
199
+ for ( const i of unresolvedOlds . values ( ) ) {
200
+ unmount ( oldBlocks [ i ] )
280
201
}
281
202
}
282
203
}
@@ -295,9 +216,10 @@ export const createFor = (
295
216
const mount = (
296
217
source : ResolvedSource ,
297
218
idx : number ,
219
+ [ item , key , index ] = getItem ( source , idx ) ,
220
+ key2 = getKey && getKey ( item , key , index ) ,
298
221
anchor : Node | undefined = parentAnchor ,
299
222
) : ForBlock => {
300
- const [ item , key , index ] = getItem ( source , idx )
301
223
const itemRef = shallowRef ( item )
302
224
// avoid creating refs if the render fn doesn't need it
303
225
const keyRef = needKey ? shallowRef ( key ) : undefined
@@ -321,23 +243,14 @@ export const createFor = (
321
243
itemRef ,
322
244
keyRef ,
323
245
indexRef ,
324
- getKey && getKey ( item , key , index ) ,
246
+ key2 ,
325
247
) )
326
248
327
249
if ( parent ) insert ( block . nodes , parent , anchor )
328
250
329
251
return block
330
252
}
331
253
332
- const tryPatchIndex = ( source : any , idx : number ) => {
333
- const block = oldBlocks [ idx ]
334
- const [ item , key , index ] = getItem ( source , idx )
335
- if ( block . key === getKey ! ( item , key , index ) ) {
336
- update ( ( newBlocks [ idx ] = block ) , item )
337
- return true
338
- }
339
- }
340
-
341
254
const update = (
342
255
{ itemRef, keyRef, indexRef } : ForBlock ,
343
256
newItem : any ,
0 commit comments