Skip to content

Commit 00b1f66

Browse files
committed
refactor(runtime-vapor): redo v-for updating
1 parent a893681 commit 00b1f66

File tree

2 files changed

+75
-142
lines changed

2 files changed

+75
-142
lines changed

packages/runtime-vapor/__tests__/for.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ describe('createFor', () => {
9494
})
9595
return span
9696
},
97-
item => item.name,
97+
item => item,
9898
)
9999
return n1
100100
}).render()

packages/runtime-vapor/src/apiCreateFor.ts

+74-141
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
toReactive,
1212
watch,
1313
} from '@vue/reactivity'
14-
import { getSequence, isArray, isObject, isString } from '@vue/shared'
14+
import { isArray, isObject, isString } from '@vue/shared'
1515
import { createComment, createTextNode } from './dom/node'
1616
import {
1717
type Block,
@@ -134,149 +134,90 @@ export const createFor = (
134134
unmount(oldBlocks[i])
135135
}
136136
} 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
137+
const commonLength = Math.min(oldLength, newLength)
138+
const oldKeyToIndexMap = new Map<any, number>()
139+
const pendingNews: [
140+
index: number,
141+
item: ReturnType<typeof getItem>,
142+
key: any,
143+
][] = []
144+
145+
let defaultAnchor: Node = parentAnchor
146+
let right = 0
147+
let left = 0
148+
149+
while (right < commonLength) {
150+
const index = newLength - right - 1
151+
const item = getItem(source, index)
152+
const key = getKey.apply(null, item)
153+
const block = oldBlocks[oldLength - right - 1]
154+
if (block.key === key) {
155+
update(block, ...item)
156+
newBlocks[index] = block
157+
right++
158+
continue
159+
}
160+
if (right !== 0) {
161+
defaultAnchor = normalizeAnchor(newBlocks[index + 1].nodes)
149162
}
163+
break
150164
}
151165

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--
166+
while (left < commonLength - right) {
167+
const item = getItem(source, left)
168+
const key = getKey.apply(null, item)
169+
const oldBlock = oldBlocks[left]
170+
const oldKey = oldBlock.key
171+
if (oldKey === key) {
172+
update((newBlocks[left] = oldBlock), item[0])
159173
} else {
160-
break
174+
pendingNews.push([left, item, key])
175+
oldKeyToIndexMap.set(oldKey, left)
161176
}
177+
left++
162178
}
163179

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-
}
180+
for (let i = left; i < oldLength - right; i++) {
181+
oldKeyToIndexMap.set(oldBlocks[i].key, i)
183182
}
184183

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++
184+
const moveOrMount = (
185+
index: number,
186+
item: ReturnType<typeof getItem>,
187+
key: any,
188+
anchor: Node,
189+
) => {
190+
const oldIndex = oldKeyToIndexMap.get(key)
191+
if (oldIndex !== undefined) {
192+
const block = (newBlocks[index] = oldBlocks[oldIndex])
193+
update(block, ...item)
194+
insert(block, parent!, anchor)
195+
oldKeyToIndexMap.delete(key)
196+
} else {
197+
mount(source, index, item, key, anchor)
196198
}
197199
}
198200

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-
}
201+
for (let i = pendingNews.length - 1; i >= 0; i--) {
202+
const [index, item, key] = pendingNews[i]
203+
moveOrMount(
204+
index,
205+
item,
206+
key,
207+
index < commonLength - 1
208+
? normalizeAnchor(newBlocks[index + 1].nodes)
209+
: defaultAnchor,
210+
)
211+
}
212212

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-
}
213+
for (let i = left; i < newLength - right; i++) {
214+
const item = getItem(source, i)
215+
const key = getKey.apply(null, item)
216+
moveOrMount(i, item, key, defaultAnchor)
217+
}
252218

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-
}
219+
for (const i of oldKeyToIndexMap.values()) {
220+
unmount(oldBlocks[i])
280221
}
281222
}
282223
}
@@ -295,9 +236,10 @@ export const createFor = (
295236
const mount = (
296237
source: ResolvedSource,
297238
idx: number,
239+
[item, key, index] = getItem(source, idx),
240+
key2 = getKey && getKey(item, key, index),
298241
anchor: Node | undefined = parentAnchor,
299242
): ForBlock => {
300-
const [item, key, index] = getItem(source, idx)
301243
const itemRef = shallowRef(item)
302244
// avoid creating refs if the render fn doesn't need it
303245
const keyRef = needKey ? shallowRef(key) : undefined
@@ -321,23 +263,14 @@ export const createFor = (
321263
itemRef,
322264
keyRef,
323265
indexRef,
324-
getKey && getKey(item, key, index),
266+
key2,
325267
))
326268

327269
if (parent) insert(block.nodes, parent, anchor)
328270

329271
return block
330272
}
331273

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-
341274
const update = (
342275
{ itemRef, keyRef, indexRef }: ForBlock,
343276
newItem: any,

0 commit comments

Comments
 (0)