From c1f6e00d4694a1e288d4a814985ca5a2704a4df8 Mon Sep 17 00:00:00 2001 From: voderl Date: Thu, 14 Aug 2025 17:45:50 +0800 Subject: [PATCH] fix(core): avoid arrayField spliceArrayState removing unexcepted fields --- packages/core/src/__tests__/array.spec.ts | 40 ++++++++++++ packages/core/src/shared/internals.ts | 74 ++++++++++++++++++----- 2 files changed, 99 insertions(+), 15 deletions(-) diff --git a/packages/core/src/__tests__/array.spec.ts b/packages/core/src/__tests__/array.spec.ts index 158284e496d..c955f8cfbe2 100644 --- a/packages/core/src/__tests__/array.spec.ts +++ b/packages/core/src/__tests__/array.spec.ts @@ -1122,3 +1122,43 @@ test('record: find form fields', () => { expect(array.record).toEqual({ array: [{ a: 1 }, { a: 2 }] }) }) + +test('array field splice array state should not destory unexpected field', () => { + const form = attach( + createForm({ + initialValues: { + array: [{ a: 1 }, { a: 2 }, { a: 3 }], + }, + }) + ) + + const array = attach( + form.createArrayField({ + name: 'array', + }) + ) + attach( + form.createField({ + name: '0', + basePath: 'array', + }) + ) + attach( + form.createField({ + name: '1', + basePath: 'array', + }) + ) + attach( + form.createField({ + name: '2', + basePath: 'array', + }) + ) + + array.remove(0) + + array.remove(0) + + expect(Object.keys(form.fields)).toEqual(['array', 'array.0']) +}) diff --git a/packages/core/src/shared/internals.ts b/packages/core/src/shared/internals.ts index e9b899f85ce..676180fae14 100644 --- a/packages/core/src/shared/internals.ts +++ b/packages/core/src/shared/internals.ts @@ -154,7 +154,17 @@ export const patchFieldStates = ( ) => { patches.forEach(({ type, address, oldAddress, payload }) => { if (type === 'remove') { - destroy(target, address, false) + if (payload) { + // When a payload is passed, the node should be deleted. However, the address may still be used. + // To avoid affecting the address order, set address to undefined. + destroyField(payload, false) + if (target[address] === payload) { + target[address] = undefined + } + } else { + // If only the address is passed without the payload, it means that the address is no longer used, so remove the address directly + delete target[address] + } } else if (type === 'update') { if (payload) { target[address] = payload @@ -169,18 +179,24 @@ export const patchFieldStates = ( }) } +export const destroyField = (field: GeneralField, forceClear = true) => { + field.dispose() + if (isDataField(field) && forceClear) { + const form = field.form + const path = field.path + form.deleteValuesIn(path) + form.deleteInitialValuesIn(path) + } +} + export const destroy = ( target: Record, address: string, forceClear = true ) => { const field = target[address] - field?.dispose() - if (isDataField(field) && forceClear) { - const form = field.form - const path = field.path - form.deleteValuesIn(path) - form.deleteInitialValuesIn(path) + if (field) { + destroyField(field, forceClear) } delete target[address] } @@ -383,19 +399,27 @@ export const spliceArrayState = ( return index >= startIndex && index < startIndex + insertCount } const isDeleteNode = (identifier: string) => { + const afterStr = identifier.substring(addrLength) + const number = afterStr.match(NumberIndexReg)?.[1] + if (number === undefined) return false + const index = Number(number) + return index >= startIndex && index < startIndex + deleteCount + } + + const isNeedCleanupNode = (identifier: string) => { const preStr = identifier.substring(0, addrLength) const afterStr = identifier.substring(addrLength) const number = afterStr.match(NumberIndexReg)?.[1] if (number === undefined) return false const index = Number(number) return ( - (index > startIndex && - !fields[ - `${preStr}${afterStr.replace(/^\.\d+/, `.${index + deleteCount}`)}` - ]) || - index === startIndex + index >= startIndex && + !fields[ + `${preStr}${afterStr.replace(/^\.\d+/, `.${index + deleteCount}`)}` + ] ) } + const moveIndex = (identifier: string) => { if (offset === 0) return identifier const preStr = identifier.substring(0, addrLength) @@ -417,9 +441,29 @@ export const spliceArrayState = ( oldAddress: identifier, payload: field, }) - } - if (isInsertNode(identifier) || isDeleteNode(identifier)) { - fieldPatches.push({ type: 'remove', address: identifier }) + if (isNeedCleanupNode(identifier)) { + fieldPatches.push({ + type: 'remove', + address: identifier, + }) + } + } else if (isInsertNode(identifier)) { + fieldPatches.push({ + type: 'remove', + address: identifier, + }) + } else if (isDeleteNode(identifier)) { + fieldPatches.push({ + type: 'remove', + address: identifier, + payload: field, + }) + if (isNeedCleanupNode(identifier)) { + fieldPatches.push({ + type: 'remove', + address: identifier, + }) + } } } })