diff --git a/docs/guide/advanced/controlled.md b/docs/guide/advanced/controlled.md index 7fb1c4eb670..ad6938533de 100644 --- a/docs/guide/advanced/controlled.md +++ b/docs/guide/advanced/controlled.md @@ -330,7 +330,6 @@ export default observer(() => { if (oldTypeRef.current !== currentType) { form.clearFormGraph('container.*') //Recycle field model - form.deleteValuesIn('container') //Clear field values } oldTypeRef.current = currentType @@ -364,7 +363,6 @@ const Custom = observer(() => { useEffect(() => { form.clearFormGraph(`${field.address}.*`) //Recycle field model - form.deleteValuesIn(field.path) //clear field values //Can be obtained asynchronously setSchema(DYNAMIC_INJECT_SCHEMA[form.values.type]) }, [form.values.type]) diff --git a/docs/guide/advanced/controlled.zh-CN.md b/docs/guide/advanced/controlled.zh-CN.md index bbc5fd57071..4be8251d37d 100644 --- a/docs/guide/advanced/controlled.zh-CN.md +++ b/docs/guide/advanced/controlled.zh-CN.md @@ -330,7 +330,6 @@ export default observer(() => { if (oldTypeRef.current !== currentType) { form.clearFormGraph('container.*') //回收字段模型 - form.deleteValuesIn('container') //清空字段值 } oldTypeRef.current = currentType @@ -364,7 +363,6 @@ const Custom = observer(() => { useEffect(() => { form.clearFormGraph(`${field.address}.*`) //回收字段模型 - form.deleteValuesIn(field.path) //清空字段值 //可以异步获取 setSchema(DYNAMIC_INJECT_SCHEMA[form.values.type]) }, [form.values.type]) diff --git a/packages/antd/src/array-base/index.tsx b/packages/antd/src/array-base/index.tsx index ef5d00c8a1a..a78cbb2fc3a 100644 --- a/packages/antd/src/array-base/index.tsx +++ b/packages/antd/src/array-base/index.tsx @@ -83,18 +83,22 @@ const useRecord = (record?: number) => { return ctx ? ctx.record : record } +const getSchemaDefaultValue = (schema: Schema) => { + if (schema?.type === 'array') return [] + if (schema?.type === 'boolean') return true + if (schema?.type === 'date') return '' + if (schema?.type === 'datetime') return '' + if (schema?.type === 'number') return 0 + if (schema?.type === 'object') return {} + if (schema?.type === 'string') return '' + return null +} + const getDefaultValue = (defaultValue: any, schema: Schema) => { if (isValid(defaultValue)) return clone(defaultValue) if (Array.isArray(schema?.items)) - return getDefaultValue(defaultValue, schema.items[0]) - if (schema?.items?.type === 'array') return [] - if (schema?.items?.type === 'boolean') return true - if (schema?.items?.type === 'date') return '' - if (schema?.items?.type === 'datetime') return '' - if (schema?.items?.type === 'number') return 0 - if (schema?.items?.type === 'object') return {} - if (schema?.items?.type === 'string') return '' - return null + return getSchemaDefaultValue(schema.items[0]) + return getSchemaDefaultValue(schema.items) } export const ArrayBase: ComposedArrayBase = (props) => { diff --git a/packages/antd/src/form-step/index.tsx b/packages/antd/src/form-step/index.tsx index 70cbb6fa73b..96c7688870c 100644 --- a/packages/antd/src/form-step/index.tsx +++ b/packages/antd/src/form-step/index.tsx @@ -1,5 +1,5 @@ import React, { Fragment } from 'react' -import { action, markRaw, model } from '@formily/reactive' +import { define, observable, action, markRaw, model } from '@formily/reactive' import { Steps } from 'antd' import cls from 'classnames' import { StepsProps, StepProps } from 'antd/lib/steps' @@ -61,11 +61,18 @@ const parseSteps = (schema: Schema) => { } const createFormStep = (defaultCurrent = 0): IFormStep => { - const env: FormStepEnv = { - form: null, - field: null, - steps: [], - } + const env: FormStepEnv = define( + { + form: null, + field: null, + steps: [], + }, + { + form: observable.ref, + field: observable.ref, + steps: observable.shallow, + } + ) const setDisplay = action.bound((target: number) => { const currentStep = env.steps[target] diff --git a/packages/core/src/__tests__/array.spec.ts b/packages/core/src/__tests__/array.spec.ts index 44dcbd2687a..c152460c5f6 100644 --- a/packages/core/src/__tests__/array.spec.ts +++ b/packages/core/src/__tests__/array.spec.ts @@ -1,5 +1,9 @@ import { createForm } from '../' -import { onFieldValueChange, onFormValuesChange } from '../effects' +import { + onFieldValueChange, + onFormInitialValuesChange, + onFormValuesChange, +} from '../effects' import { DataField } from '../types' import { attach } from './shared' @@ -420,10 +424,12 @@ test('array field move api with children', async () => { test('array field remove memo leak', async () => { const handler = jest.fn() const valuesChange = jest.fn() + const initialValuesChange = jest.fn() const form = attach( createForm({ effects() { onFormValuesChange(valuesChange) + onFormInitialValuesChange(initialValuesChange) onFieldValueChange('*', handler) }, }) @@ -450,6 +456,7 @@ test('array field remove memo leak', async () => { ) expect(handler).toBeCalledTimes(0) expect(valuesChange).toBeCalledTimes(4) + expect(initialValuesChange).toBeCalledTimes(0) }) test('nest array remove', async () => { diff --git a/packages/core/src/__tests__/field.spec.ts b/packages/core/src/__tests__/field.spec.ts index 62dbc43abfb..fedd080739d 100644 --- a/packages/core/src/__tests__/field.spec.ts +++ b/packages/core/src/__tests__/field.spec.ts @@ -2092,3 +2092,18 @@ test('empty string or number or null value need rewrite default value', () => { expect(form.values.dd).toEqual(123) expect(form.values.ee).toEqual(null) }) + +test('destroy field need auto remove initialValues', () => { + const form = attach(createForm()) + const aa = attach( + form.createField({ + name: 'aa', + initialValue: 'test', + }) + ) + expect(form.initialValues.aa).toEqual('test') + expect(form.values.aa).toEqual('test') + aa.destroy() + expect(form.initialValues.aa).toBeUndefined() + expect(form.values.aa).toBeUndefined() +}) diff --git a/packages/core/src/shared/internals.ts b/packages/core/src/shared/internals.ts index 7f8f902e641..7c7153299af 100644 --- a/packages/core/src/shared/internals.ts +++ b/packages/core/src/shared/internals.ts @@ -158,7 +158,7 @@ export const patchFieldStates = ( ) => { patches.forEach(({ type, address, oldAddress, payload }) => { if (type === 'remove') { - destroy(target, address) + destroy(target, address, false) } else if (type === 'update') { if (payload) { target[address] = payload @@ -176,9 +176,17 @@ export const patchFieldStates = ( export const destroy = ( target: Record, - address: string + address: string, + removeValue = true ) => { - target[address]?.dispose() + const field = target[address] + field?.dispose() + if (isDataField(field) && removeValue) { + const form = field.form + const path = field.path + form.deleteValuesIn(path) + form.deleteInitialValuesIn(path) + } delete target[address] } diff --git a/packages/next/src/array-base/index.tsx b/packages/next/src/array-base/index.tsx index a99471df6ef..baa379b2865 100644 --- a/packages/next/src/array-base/index.tsx +++ b/packages/next/src/array-base/index.tsx @@ -83,18 +83,22 @@ const useRecord = (record?: number) => { return ctx ? ctx.record : record } +const getSchemaDefaultValue = (schema: Schema) => { + if (schema?.type === 'array') return [] + if (schema?.type === 'boolean') return true + if (schema?.type === 'date') return '' + if (schema?.type === 'datetime') return '' + if (schema?.type === 'number') return 0 + if (schema?.type === 'object') return {} + if (schema?.type === 'string') return '' + return null +} + const getDefaultValue = (defaultValue: any, schema: Schema) => { if (isValid(defaultValue)) return clone(defaultValue) if (Array.isArray(schema?.items)) - return getDefaultValue(defaultValue, schema.items[0]) - if (schema?.items?.type === 'array') return [] - if (schema?.items?.type === 'boolean') return true - if (schema?.items?.type === 'date') return '' - if (schema?.items?.type === 'datetime') return '' - if (schema?.items?.type === 'number') return 0 - if (schema?.items?.type === 'object') return {} - if (schema?.items?.type === 'string') return '' - return null + return getSchemaDefaultValue(schema.items[0]) + return getSchemaDefaultValue(schema.items) } export const ArrayBase: ComposedArrayBase = (props) => { diff --git a/packages/next/src/form-step/index.tsx b/packages/next/src/form-step/index.tsx index 0522de9e926..ebb098800a2 100644 --- a/packages/next/src/form-step/index.tsx +++ b/packages/next/src/form-step/index.tsx @@ -1,5 +1,5 @@ import React, { Fragment } from 'react' -import { model, markRaw, action } from '@formily/reactive' +import { define, observable, model, markRaw, action } from '@formily/reactive' import cls from 'classnames' import { StepProps as StepsProps, @@ -64,11 +64,18 @@ const parseSteps = (schema: Schema) => { } const createFormStep = (defaultCurrent = 0): IFormStep => { - const env: FormStepEnv = { - form: null, - field: null, - steps: [], - } + const env: FormStepEnv = define( + { + form: null, + field: null, + steps: [], + }, + { + form: observable.ref, + field: observable.ref, + steps: observable.shallow, + } + ) const setDisplay = action.bound((target: number) => { const currentStep = env.steps[target]