diff --git a/packages/runtime-vapor/__tests__/for.spec.ts b/packages/runtime-vapor/__tests__/for.spec.ts index db91b6a62da..ca5b7f30364 100644 --- a/packages/runtime-vapor/__tests__/for.spec.ts +++ b/packages/runtime-vapor/__tests__/for.spec.ts @@ -417,12 +417,12 @@ describe('createFor', () => { '
  • 0. 1
  • 1. 2
  • 2. 3
  • 3. 4
  • ', ) - // change deep value should not update + // change list.value[0].name = 'a' setList() await nextTick() expect(host.innerHTML).toBe( - '
  • 0. 1
  • 1. 2
  • 2. 3
  • 3. 4
  • ', + '
  • 0. a
  • 1. 2
  • 2. 3
  • 3. 4
  • ', ) // remove @@ -430,7 +430,7 @@ describe('createFor', () => { setList() await nextTick() expect(host.innerHTML).toBe( - '
  • 0. 1
  • 1. 3
  • 2. 4
  • ', + '
  • 0. a
  • 1. 3
  • 2. 4
  • ', ) // clear diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts index 62529149ad4..f6a694714d5 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -11,7 +11,16 @@ import { toReactive, toReadonly, } from '@vue/reactivity' -import { getSequence, isArray, isObject, isString } from '@vue/shared' +import { + extend, + getSequence, + isArray, + isMap, + isObject, + isSet, + isString, + looseEqual, +} from '@vue/shared' import { createComment, createTextNode } from './dom/node' import { type Block, @@ -84,6 +93,7 @@ export const createFor = ( } let isMounted = false + let prevNeedsWrap: boolean let oldBlocks: ForBlock[] = [] let newBlocks: ForBlock[] let parent: ParentNode | undefined | null @@ -99,7 +109,8 @@ export const createFor = ( } const renderList = () => { - const source = normalizeSource(src()) + const source = normalizeSource(src(), prevNeedsWrap) + prevNeedsWrap = source.needsWrap const newLength = source.values.length const oldLength = oldBlocks.length newBlocks = new Array(newLength) @@ -132,7 +143,7 @@ export const createFor = ( // unkeyed fast path const commonLength = Math.min(newLength, oldLength) for (let i = 0; i < commonLength; i++) { - update((newBlocks[i] = oldBlocks[i]), getItem(source, i)[0]) + update(source, (newBlocks[i] = oldBlocks[i]), getItem(source, i)[0]) } for (let i = oldLength; i < newLength; i++) { mount(source, i) @@ -249,6 +260,7 @@ export const createFor = ( moved = true } update( + source, (newBlocks[newIndex] = prevBlock), ...getItem(source, newIndex), ) @@ -336,22 +348,27 @@ export const createFor = ( return block } - const tryPatchIndex = (source: any, idx: number) => { + const tryPatchIndex = (source: ResolvedSource, idx: number) => { const block = oldBlocks[idx] const [item, key, index] = getItem(source, idx) if (block.key === getKey!(item, key, index)) { - update((newBlocks[idx] = block), item) + update(source, (newBlocks[idx] = block), item) return true } } const update = ( + { needsWrap }: ResolvedSource, { itemRef, keyRef, indexRef }: ForBlock, newItem: any, newKey?: any, newIndex?: any, ) => { - if (newItem !== itemRef.value) { + if ( + needsWrap + ? newItem !== itemRef.value + : !looseEqual(newItem, itemRef.value) + ) { itemRef.value = newItem } if (keyRef && newKey !== undefined && newKey !== keyRef.value) { @@ -393,9 +410,8 @@ export function createForSlots( return slots } -function normalizeSource(source: any): ResolvedSource { +function normalizeSource(source: any, needsWrap = false): ResolvedSource { let values = source - let needsWrap = false let isReadonlySource = false let keys if (isArray(source)) { @@ -431,6 +447,18 @@ function normalizeSource(source: any): ResolvedSource { } } +function shallowClone(val: any) { + return Array.isArray(val) + ? val.slice() + : isObject(val) + ? extend({}, val) + : isMap(val) + ? new Map(val) + : isSet(val) + ? new Set(val) + : val +} + function getItem( { keys, values, needsWrap, isReadonlySource }: ResolvedSource, idx: number, @@ -439,7 +467,7 @@ function getItem( ? isReadonlySource ? toReadonly(toReactive(values[idx])) : toReactive(values[idx]) - : values[idx] + : shallowClone(values[idx]) if (keys) { return [value, keys[idx], idx] } else {