From 3d3aba56a37ba8624adfbc07506490fd78a609f3 Mon Sep 17 00:00:00 2001 From: zhiyuanzmj <260480378@qq.com> Date: Mon, 3 Mar 2025 12:58:11 +0800 Subject: [PATCH 1/4] feat(runtime-vapor): use shallow clone to support shallowRef in v-for --- packages/runtime-vapor/__tests__/for.spec.ts | 6 +-- packages/runtime-vapor/src/apiCreateFor.ts | 39 +++++++++++++++++--- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/packages/runtime-vapor/__tests__/for.spec.ts b/packages/runtime-vapor/__tests__/for.spec.ts index 7ba6023b1e9..978750b4107 100644 --- a/packages/runtime-vapor/__tests__/for.spec.ts +++ b/packages/runtime-vapor/__tests__/for.spec.ts @@ -410,12 +410,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 @@ -423,7 +423,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 19653cd5daa..7262ba31fa8 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -9,7 +9,16 @@ import { shallowRef, toReactive, } 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, @@ -116,7 +125,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) @@ -233,6 +242,7 @@ export const createFor = ( moved = true } update( + source, (newBlocks[newIndex] = prevBlock), ...getItem(source, newIndex), ) @@ -320,22 +330,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) { @@ -403,11 +418,23 @@ function normalizeSource(source: any): ResolvedSource { return { values, needsWrap, keys } } +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 }: ResolvedSource, idx: number, ): [item: any, key: any, index?: number] { - const value = needsWrap ? toReactive(values[idx]) : values[idx] + const value = needsWrap ? toReactive(values[idx]) : shallowClone(values[idx]) if (keys) { return [value, keys[idx], idx] } else { From 58e18dbdbf5e3287da7d9373f82fdf50b53782a7 Mon Sep 17 00:00:00 2001 From: zhiyuanzmj <260480378@qq.com> Date: Mon, 3 Mar 2025 20:31:05 +0800 Subject: [PATCH 2/4] fix: e2e-test --- packages/runtime-vapor/src/apiCreateFor.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts index 7262ba31fa8..8601e5e3d01 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -78,6 +78,7 @@ export const createFor = ( // hydrationNode?: Node, ): VaporFragment => { let isMounted = false + let needsWrapCache: boolean let oldBlocks: ForBlock[] = [] let newBlocks: ForBlock[] let parent: ParentNode | undefined | null @@ -92,7 +93,10 @@ export const createFor = ( } const renderList = () => { - const source = normalizeSource(src()) + const source = normalizeSource(src(), needsWrapCache) + if (needsWrapCache === undefined) { + needsWrapCache = source.needsWrap + } const newLength = source.values.length const oldLength = oldBlocks.length newBlocks = new Array(newLength) @@ -387,13 +391,14 @@ export function createForSlots( return slots } -function normalizeSource(source: any): ResolvedSource { +function normalizeSource(source: any, needsWrap?: boolean): ResolvedSource { let values = source - let needsWrap = false let keys if (isArray(source)) { if (isReactive(source)) { - needsWrap = !isShallow(source) + if (needsWrap === undefined) { + needsWrap = !isShallow(source) + } values = shallowReadArray(source) } } else if (isString(source)) { @@ -415,7 +420,7 @@ function normalizeSource(source: any): ResolvedSource { } } } - return { values, needsWrap, keys } + return { values, needsWrap: !!needsWrap, keys } } function shallowClone(val: any) { From eef6b8aef858028191f949abf945bdc84cb1b9d2 Mon Sep 17 00:00:00 2001 From: zhiyuanzmj <260480378@qq.com> Date: Tue, 4 Mar 2025 09:58:34 +0800 Subject: [PATCH 3/4] feat: use prevNeedsWrap --- packages/runtime-vapor/src/apiCreateFor.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts index 8601e5e3d01..2a3c559cb3f 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -78,7 +78,7 @@ export const createFor = ( // hydrationNode?: Node, ): VaporFragment => { let isMounted = false - let needsWrapCache: boolean + let prevNeedsWrap: boolean let oldBlocks: ForBlock[] = [] let newBlocks: ForBlock[] let parent: ParentNode | undefined | null @@ -93,10 +93,8 @@ export const createFor = ( } const renderList = () => { - const source = normalizeSource(src(), needsWrapCache) - if (needsWrapCache === undefined) { - needsWrapCache = source.needsWrap - } + const source = normalizeSource(src(), prevNeedsWrap) + prevNeedsWrap = source.needsWrap const newLength = source.values.length const oldLength = oldBlocks.length newBlocks = new Array(newLength) @@ -396,9 +394,7 @@ function normalizeSource(source: any, needsWrap?: boolean): ResolvedSource { let keys if (isArray(source)) { if (isReactive(source)) { - if (needsWrap === undefined) { - needsWrap = !isShallow(source) - } + needsWrap = !isShallow(source) values = shallowReadArray(source) } } else if (isString(source)) { From f019b7defcd700352a73162935ec60e93caf0cea Mon Sep 17 00:00:00 2001 From: zhiyuanzmj <260480378@qq.com> Date: Tue, 4 Mar 2025 12:26:38 +0800 Subject: [PATCH 4/4] fix: typo --- packages/runtime-vapor/src/apiCreateFor.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts index 2a3c559cb3f..48ce724d975 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -389,7 +389,7 @@ export function createForSlots( return slots } -function normalizeSource(source: any, needsWrap?: boolean): ResolvedSource { +function normalizeSource(source: any, needsWrap = false): ResolvedSource { let values = source let keys if (isArray(source)) { @@ -416,7 +416,7 @@ function normalizeSource(source: any, needsWrap?: boolean): ResolvedSource { } } } - return { values, needsWrap: !!needsWrap, keys } + return { values, needsWrap, keys } } function shallowClone(val: any) {