Skip to content

Commit c287c97

Browse files
committed
fix(runtime-vapor): refresh same v-for item refs
1 parent cf96e99 commit c287c97

2 files changed

Lines changed: 122 additions & 7 deletions

File tree

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

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -426,20 +426,20 @@ describe('createFor', () => {
426426
'<li>0. 1</li><li>1. 2</li><li>2. 3</li><li>3. 4</li><!--for-->',
427427
)
428428

429-
// change deep value should not update
429+
// change deep value and refresh source
430430
list.value[0].name = 'a'
431431
setList()
432432
await nextTick()
433433
expect(host.innerHTML).toBe(
434-
'<li>0. 1</li><li>1. 2</li><li>2. 3</li><li>3. 4</li><!--for-->',
434+
'<li>0. a</li><li>1. 2</li><li>2. 3</li><li>3. 4</li><!--for-->',
435435
)
436436

437437
// remove
438438
list.value.splice(1, 1)
439439
setList()
440440
await nextTick()
441441
expect(host.innerHTML).toBe(
442-
'<li>0. 1</li><li>1. 3</li><li>2. 4</li><!--for-->',
442+
'<li>0. a</li><li>1. 3</li><li>2. 4</li><!--for-->',
443443
)
444444

445445
// clear
@@ -448,6 +448,82 @@ describe('createFor', () => {
448448
expect(host.innerHTML).toBe('<!--for-->')
449449
})
450450

451+
test('should update same item references when source is refreshed', async () => {
452+
let rawList = [{ number: 0 }, { number: 1 }]
453+
const list = shallowRef(rawList)
454+
455+
const { host } = define(() => {
456+
const n1 = createFor(
457+
() => list.value,
458+
item => {
459+
const span = document.createElement('li')
460+
renderEffect(() => {
461+
span.textContent = JSON.stringify(item.value)
462+
})
463+
return span
464+
},
465+
(_item, key) => `${key}-test`,
466+
)
467+
return n1
468+
}).render()
469+
470+
expect(host.innerHTML).toBe(
471+
'<li>{"number":0}</li><li>{"number":1}</li><!--for-->',
472+
)
473+
474+
rawList[0].number = 2
475+
list.value = rawList.slice()
476+
await nextTick()
477+
expect(host.innerHTML).toBe(
478+
'<li>{"number":2}</li><li>{"number":1}</li><!--for-->',
479+
)
480+
481+
list.value[0].number = 3
482+
triggerRef(list)
483+
await nextTick()
484+
expect(host.innerHTML).toBe(
485+
'<li>{"number":3}</li><li>{"number":1}</li><!--for-->',
486+
)
487+
488+
rawList = [{ number: 0 }, { number: 1 }]
489+
list.value = rawList
490+
await nextTick()
491+
expect(host.innerHTML).toBe(
492+
'<li>{"number":0}</li><li>{"number":1}</li><!--for-->',
493+
)
494+
})
495+
496+
test('should update same reactive item references when source is replaced', async () => {
497+
const rawList = [{ number: 0 }, { number: 1 }]
498+
const list = ref(rawList)
499+
500+
const { host } = define(() => {
501+
const n1 = createFor(
502+
() => list.value,
503+
item => {
504+
const span = document.createElement('li')
505+
renderEffect(() => {
506+
span.textContent = JSON.stringify(item.value)
507+
})
508+
return span
509+
},
510+
(_item, key) => `${key}-test`,
511+
)
512+
return n1
513+
}).render()
514+
515+
expect(host.innerHTML).toBe(
516+
'<li>{"number":0}</li><li>{"number":1}</li><!--for-->',
517+
)
518+
519+
rawList[0].number = 2
520+
list.value = rawList.slice()
521+
await nextTick()
522+
expect(host.innerHTML).toBe(
523+
'<li>{"number":2}</li><li>{"number":1}</li><!--for-->',
524+
)
525+
})
526+
451527
test('should optimize call frequency during list operations', async () => {
452528
let sourceCalledTimes = 0
453529
let renderCalledTimes = 0
@@ -519,6 +595,14 @@ describe('createFor', () => {
519595
await nextTick()
520596
expectCalledTimesToBe('Update every 10th row', 0, 0, length() / 10, 0)
521597

598+
// Replace a row with the same key
599+
list.value[0] = {
600+
id: list.value[0].id,
601+
label: list.value[0].label + 10000,
602+
}
603+
await nextTick()
604+
expectCalledTimesToBe('Replace a row with the same key', 1, 0, 1, 0)
605+
522606
// Append rows
523607
list.value.push(...createItems(100))
524608
await nextTick()
@@ -638,6 +722,15 @@ describe('createFor', () => {
638722
await nextTick()
639723
expectCalledTimesToBe('Update every 10th row', 0, 0, length() / 10, 0)
640724

725+
// Replace a row with the same key
726+
list.value[0] = {
727+
id: list.value[0].id,
728+
label: shallowRef(list.value[0].label.value + 10000),
729+
}
730+
triggerRef(list)
731+
await nextTick()
732+
expectCalledTimesToBe('Replace a row with the same key', 1, 0, 1, 0)
733+
641734
// Append rows
642735
list.value.push(...createItems(100))
643736
triggerRef(list)

packages/runtime-vapor/src/apiCreateFor.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
shallowRef,
1010
toReactive,
1111
toReadonly,
12+
triggerRef,
1213
watch,
1314
} from '@vue/reactivity'
1415
import { isArray, isObject, isString } from '@vue/shared'
@@ -164,15 +165,21 @@ export const createFor = (
164165
} else if (!getKey) {
165166
// unkeyed fast path
166167
const commonLength = Math.min(newLength, oldLength)
168+
let shouldTriggerSameItems = oldLength === newLength
167169
for (let i = 0; i < commonLength; i++) {
168-
update((newBlocks[i] = oldBlocks[i]), getItem(source, i)[0])
170+
if (update((newBlocks[i] = oldBlocks[i]), getItem(source, i)[0])) {
171+
shouldTriggerSameItems = false
172+
}
169173
}
170174
for (let i = oldLength; i < newLength; i++) {
171175
mount(source, i)
172176
}
173177
for (let i = newLength; i < oldLength; i++) {
174178
unmount(oldBlocks[i])
175179
}
180+
if (shouldTriggerSameItems) {
181+
triggerSameItemObjectRefs(newBlocks)
182+
}
176183
} else {
177184
if (__DEV__) {
178185
const keyToIndexMap: Map<any, number> = new Map()
@@ -203,17 +210,21 @@ export const createFor = (
203210
let endOffset = 0
204211
let queuedBlocksLength = 0
205212
let oldKeyIndexPairsLength = 0
213+
let shouldTriggerSameItems = oldLength === newLength
206214

207215
while (endOffset < commonLength) {
208216
const index = newLength - endOffset - 1
209217
const item = getItem(source, index)
210218
const key = getKey(...item)
211219
const existingBlock = oldBlocks[oldLength - endOffset - 1]
212220
if (existingBlock.key !== key) break
213-
update(existingBlock, ...item)
221+
if (update(existingBlock, ...item)) {
222+
shouldTriggerSameItems = false
223+
}
214224
newBlocks[index] = existingBlock
215225
endOffset++
216226
}
227+
if (endOffset !== commonLength) shouldTriggerSameItems = false
217228

218229
const e1 = commonLength - endOffset
219230
const e2 = oldLength - endOffset
@@ -357,9 +368,11 @@ export const createFor = (
357368
block.prevAnchor = block.next = block.prev = undefined
358369
}
359370
}
371+
if (shouldTriggerSameItems) {
372+
triggerSameItemObjectRefs(newBlocks)
373+
}
360374
}
361375
}
362-
363376
frag.nodes = [(oldBlocks = newBlocks)]
364377
if (parentAnchor) frag.nodes.push(parentAnchor)
365378

@@ -512,7 +525,8 @@ export const createFor = (
512525
newKey?: any,
513526
newIndex?: any,
514527
) => {
515-
if (newItem !== itemRef.value) {
528+
const itemChanged = newItem !== itemRef.value
529+
if (itemChanged) {
516530
itemRef.value = newItem
517531
}
518532
if (keyRef && newKey !== undefined && newKey !== keyRef.value) {
@@ -521,6 +535,14 @@ export const createFor = (
521535
if (indexRef && newIndex !== undefined && newIndex !== indexRef.value) {
522536
indexRef.value = newIndex
523537
}
538+
return itemChanged
539+
}
540+
541+
function triggerSameItemObjectRefs(blocks: ForBlock[]): void {
542+
for (let i = 0; i < blocks.length; i++) {
543+
const itemRef = blocks[i].itemRef
544+
if (isObject(itemRef.value)) triggerRef(itemRef)
545+
}
524546
}
525547

526548
const unmount = (block: ForBlock, doRemove = true, doDeregister = true) => {

0 commit comments

Comments
 (0)