From 8dc609f948673f26be4d001462c4ca09a554d3b4 Mon Sep 17 00:00:00 2001 From: Janry Date: Sun, 19 Jan 2020 13:28:26 +0800 Subject: [PATCH] fix(@uform/core/react): fix #613 #615 (#618) * fix(@uform/shared): fix isValid * fix(@uform/core): fix visible changed but not trigger onChange callback * fix(@uform/core): fix #613 and #615 * refactor(@uform/core): improve code --- .../__snapshots__/index.spec.ts.snap | 109 ++++++++++++++++++ packages/core/src/__tests__/index.spec.ts | 52 ++++++++- packages/core/src/index.ts | 97 +++++++++++----- packages/core/src/shared/model.ts | 51 +++++++- packages/core/src/state/field.ts | 15 ++- packages/core/src/state/virtual-field.ts | 4 +- packages/core/src/types.ts | 13 +++ .../__snapshots__/markup.spec.tsx.snap | 2 + .../__snapshots__/register.spec.tsx.snap | 3 + .../src/components/SchemaField.tsx | 1 + packages/react/src/components/FieldList.tsx | 17 +++ packages/react/src/hooks/useDirty.ts | 2 +- packages/react/src/hooks/useField.ts | 29 ++--- packages/react/src/hooks/useVirtualField.ts | 42 ++++--- packages/react/src/index.ts | 1 + packages/shared/src/isEmpty.ts | 2 +- 16 files changed, 360 insertions(+), 80 deletions(-) create mode 100644 packages/react/src/components/FieldList.tsx diff --git a/packages/core/src/__tests__/__snapshots__/index.spec.ts.snap b/packages/core/src/__tests__/__snapshots__/index.spec.ts.snap index a2f234e2cde..e6653bdfe10 100644 --- a/packages/core/src/__tests__/__snapshots__/index.spec.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/index.spec.ts.snap @@ -57,6 +57,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -93,6 +94,7 @@ Object { }, "bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -158,6 +160,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -194,6 +197,7 @@ Object { }, "bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -259,6 +263,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -295,6 +300,7 @@ Object { }, "bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -360,6 +366,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -396,6 +403,7 @@ Object { }, "bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -510,6 +518,7 @@ Object { }, "a": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -569,6 +578,7 @@ Object { }, "a": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -628,6 +638,7 @@ Object { }, "a": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -687,6 +698,7 @@ Object { }, "a": Object { "active": true, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -752,6 +764,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -788,6 +801,7 @@ Object { }, "bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -852,6 +866,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -888,6 +903,7 @@ Object { }, "bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -947,6 +963,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -998,6 +1015,7 @@ Object { }, "aa.bb.cc": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1059,6 +1077,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1103,6 +1122,7 @@ Object { }, "aa.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1143,6 +1163,7 @@ Object { }, "aa.bb.cc": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1202,6 +1223,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1246,6 +1268,7 @@ Object { }, "aa.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1286,6 +1309,7 @@ Object { }, "aa.bb.cc": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1367,6 +1391,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1430,6 +1455,7 @@ Object { }, "aa.0": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1475,6 +1501,7 @@ Object { }, "aa.0.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1511,6 +1538,7 @@ Object { }, "aa.0.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1547,6 +1575,7 @@ Object { }, "aa.1": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1592,6 +1621,7 @@ Object { }, "aa.1.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1628,6 +1658,7 @@ Object { }, "aa.1.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1705,6 +1736,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1760,6 +1792,7 @@ Object { }, "aa.0": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1805,6 +1838,7 @@ Object { }, "aa.0.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1841,6 +1875,7 @@ Object { }, "aa.0.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1922,6 +1957,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -1985,6 +2021,7 @@ Object { }, "aa.0": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2030,6 +2067,7 @@ Object { }, "aa.0.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2066,6 +2104,7 @@ Object { }, "aa.0.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2102,6 +2141,7 @@ Object { }, "aa.1": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2147,6 +2187,7 @@ Object { }, "aa.1.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2183,6 +2224,7 @@ Object { }, "aa.1.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2249,6 +2291,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2295,6 +2338,7 @@ Object { }, "aa.0": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2331,6 +2375,7 @@ Object { }, "aa.0.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2367,6 +2412,7 @@ Object { }, "aa.0.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2403,6 +2449,7 @@ Object { }, "aa.1": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2443,6 +2490,7 @@ Object { }, "aa.1.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2479,6 +2527,7 @@ Object { }, "aa.1.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2544,6 +2593,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2588,6 +2638,7 @@ Object { }, "aa.0": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2628,6 +2679,7 @@ Object { }, "aa.0.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2664,6 +2716,7 @@ Object { }, "aa.0.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2730,6 +2783,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2776,6 +2830,7 @@ Object { }, "aa.0": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2812,6 +2867,7 @@ Object { }, "aa.0.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2848,6 +2904,7 @@ Object { }, "aa.0.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2884,6 +2941,7 @@ Object { }, "aa.1": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2924,6 +2982,7 @@ Object { }, "aa.1.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -2960,6 +3019,7 @@ Object { }, "aa.1.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3024,6 +3084,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3069,6 +3130,7 @@ Object { }, "aa.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3105,6 +3167,7 @@ Object { }, "aa.cc": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3169,6 +3232,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3214,6 +3278,7 @@ Object { }, "aa.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3250,6 +3315,7 @@ Object { }, "aa.cc": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3319,6 +3385,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3364,6 +3431,7 @@ Object { }, "aa.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3400,6 +3468,7 @@ Object { }, "aa.cc": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3481,6 +3550,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3544,6 +3614,7 @@ Object { }, "aa.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3601,6 +3672,7 @@ Object { }, "aa.bb.0": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3643,6 +3715,7 @@ Object { }, "aa.bb.0.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3679,6 +3752,7 @@ Object { }, "aa.bb.1": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3721,6 +3795,7 @@ Object { }, "aa.bb.1.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3795,6 +3870,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3844,6 +3920,7 @@ Object { }, "aa.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3932,6 +4009,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -3995,6 +4073,7 @@ Object { }, "aa.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4052,6 +4131,7 @@ Object { }, "aa.bb.0": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4094,6 +4174,7 @@ Object { }, "aa.bb.0.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4130,6 +4211,7 @@ Object { }, "aa.bb.1": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4172,6 +4254,7 @@ Object { }, "aa.bb.1.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4253,6 +4336,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4316,6 +4400,7 @@ Object { }, "aa.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4373,6 +4458,7 @@ Object { }, "aa.bb.0": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4415,6 +4501,7 @@ Object { }, "aa.bb.0.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4451,6 +4538,7 @@ Object { }, "aa.bb.1": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4493,6 +4581,7 @@ Object { }, "aa.bb.1.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4574,6 +4663,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4637,6 +4727,7 @@ Object { }, "aa.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4694,6 +4785,7 @@ Object { }, "aa.bb.0": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4736,6 +4828,7 @@ Object { }, "aa.bb.0.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4772,6 +4865,7 @@ Object { }, "aa.bb.1": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4814,6 +4908,7 @@ Object { }, "aa.bb.1.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4884,6 +4979,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4938,6 +5034,7 @@ Object { }, "aa.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -4988,6 +5085,7 @@ Object { }, "aa.bb.0": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -5028,6 +5126,7 @@ Object { }, "aa.bb.0.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -5064,6 +5163,7 @@ Object { }, "aa.bb.1": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -5104,6 +5204,7 @@ Object { }, "aa.bb.1.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -5174,6 +5275,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -5228,6 +5330,7 @@ Object { }, "aa.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -5278,6 +5381,7 @@ Object { }, "aa.bb.0": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -5318,6 +5422,7 @@ Object { }, "aa.bb.0.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -5354,6 +5459,7 @@ Object { }, "aa.bb.1": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -5394,6 +5500,7 @@ Object { }, "aa.bb.1.aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -5457,6 +5564,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, @@ -5497,6 +5605,7 @@ Object { }, "aa.bb": Object { "active": false, + "dataType": "any", "display": true, "displayName": "FieldState", "editable": true, diff --git a/packages/core/src/__tests__/index.spec.ts b/packages/core/src/__tests__/index.spec.ts index 063f47dca06..02970a16e6f 100644 --- a/packages/core/src/__tests__/index.spec.ts +++ b/packages/core/src/__tests__/index.spec.ts @@ -111,6 +111,33 @@ describe('createForm', () => { expect(form.getFormGraph()).toMatchSnapshot() }) + const sleep = (d=1000)=>new Promise((resolve)=>{ + setTimeout(()=>{ + resolve() + },d) + }) + + test('invalid initialValue will not trigger validate', async () => { + const form = createForm() + const field = form.registerField({ + name: 'aa', + rules:[{ + required:true + }] + }) + const mutators = form.createMutators(field) + field.subscribe(() => { + mutators.validate({ throwErrors: false }) + }) + form.setFormState(state => { + state.initialValues = { + aa: null + } + }) + await sleep(10) + expect(field.getState(state=>state.errors).length).toEqual(1) + }) + test('lifecycles', () => { const onFormInit = jest.fn() const onFieldInit = jest.fn() @@ -437,9 +464,9 @@ describe('clearErrors', () => { expect(form.getFormState(state => state.errors)).toEqual([]) }) - test('wildcard path', async () => { }) + test('wildcard path', async () => {}) - test('effect', async () => { }) + test('effect', async () => {}) }) describe('validate', () => { @@ -487,7 +514,7 @@ describe('validate', () => { try { await form.submit() - } catch (e) { } + } catch (e) {} expect(onValidateFailedTrigger).toBeCalledTimes(1) }) @@ -515,7 +542,7 @@ describe('validate', () => { }) // CustomValidator error try { await form.submit() - } catch (e) { } + } catch (e) {} expect(onValidateFailedTrigger).toBeCalledTimes(1) }) @@ -1560,6 +1587,23 @@ describe('major sences', () => { expect(form.getFormGraph()).toMatchSnapshot() }) + test('visible onChange', () => { + const onChangeHandler = jest.fn() + const form = createForm({ + initialValues: { + aa: 123 + }, + onChange: onChangeHandler + }) + form.registerField({ + name: 'aa' + }) + form.setFieldState('aa', state => { + state.visible = false + }) + expect(onChangeHandler).toBeCalledTimes(1) + }) + test('deep nested visible(root)', () => { const form = createForm() form.registerField({ path: 'aa', value: {} }) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 5e1e6ae4230..b8168097d63 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -96,6 +96,12 @@ export function createForm( state.initialValue = initialValue if (!isValid(state.value)) { state.value = initialValue + } else if ( + /array/gi.test(state.dataType) && + state.value && + state.value.length === 0 + ) { + state.value = initialValue } } } @@ -112,7 +118,7 @@ export function createForm( const updateFields = (field: IField | IVirtualField) => { if (isField(field)) { field.setState(state => { - if (state.visible) { + if (state.visible || state.unmounted) { if (valuesChanged) { syncFieldValues(state) } @@ -122,14 +128,14 @@ export function createForm( } else { //缓存变化,等字段重新显示的时候再执行 if (valuesChanged) { - env.visiblePendingFields[state.name] = - env.visiblePendingFields[state.name] || {} - env.visiblePendingFields[state.name].values = true + env.hiddenPendingFields[state.name] = + env.hiddenPendingFields[state.name] || {} + env.hiddenPendingFields[state.name].values = true } if (initialValuesChanged) { - env.visiblePendingFields[state.name] = - env.visiblePendingFields[state.name] || {} - env.visiblePendingFields[state.name].initialValues = true + env.hiddenPendingFields[state.name] = + env.hiddenPendingFields[state.name] || {} + env.hiddenPendingFields[state.name].initialValues = true } } }) @@ -227,6 +233,28 @@ export function createForm( const errorsChanged = field.isDirty('errors') const userUpdateFieldPath = env.userUpdateFields[env.userUpdateFields.length - 1] + + const syncField = () => { + if (env.hiddenPendingFields[published.name]) { + field.setState((state: IFieldState) => { + if (env.hiddenPendingFields[state.name].values) { + syncFieldValues(state) + } + if (env.hiddenPendingFields[state.name].initialValues) { + syncFieldIntialValues(state) + } + delete env.hiddenPendingFields[state.name] + }) + } + } + + const notifyFormValuesChange = () => { + if (isFn(options.onChange)) { + options.onChange(state.getSourceState(state => clone(state.values))) + } + heart.publish(LifeCycleTypes.ON_FORM_VALUES_CHANGE, state) + } + if (initializedChanged) { heart.publish(LifeCycleTypes.ON_FIELD_INIT, field) const isEmptyValue = !isValid(published.value) @@ -239,34 +267,34 @@ export function createForm( }) } } + const wasHidden = + published.visible == false || published.unmounted === true if (valueChanged) { - userUpdating(field, () => { - setFormValuesIn(path, published.value) - }) + if (!wasHidden) { + userUpdating(field, () => { + setFormValuesIn(path, published.value) + }) + } heart.publish(LifeCycleTypes.ON_FIELD_VALUE_CHANGE, field) } if (initialValueChanged) { - setFormInitialValuesIn(path, published.initialValue) + if (!wasHidden) { + setFormInitialValuesIn(path, published.initialValue) + } heart.publish(LifeCycleTypes.ON_FIELD_INITIAL_VALUE_CHANGE, field) } if (displayChanged || visibleChanged) { if (visibleChanged) { - if (!published.visible) { - deleteFormValuesIn(path, true) - } else { - setFormValuesIn(path, published.value) - if (env.visiblePendingFields[published.name]) { - field.setState((state: IFieldState) => { - if (env.visiblePendingFields[state.name].values) { - syncFieldValues(state) - } - if (env.visiblePendingFields[state.name].initialValues) { - syncFieldIntialValues(state) - } - delete env.visiblePendingFields[state.name] - }) + userUpdating(field, () => { + if (!published.visible) { + deleteFormValuesIn(path, true) + //考虑到隐藏删值,不应该同步子树,但是需要触发表单变化事件 + notifyFormValuesChange() + } else { + setFormValuesIn(path, published.value) + syncField() } - } + }) } graph.eachChildren(path, childState => { childState.setState((state: IFieldState) => { @@ -286,8 +314,11 @@ export function createForm( userUpdating(field, () => { if (published.unmounted) { deleteFormValuesIn(path, true) + //考虑到隐藏删值,不应该同步子树,但是需要触发表单变化事件 + notifyFormValuesChange() } else { setFormValuesIn(path, published.value) + syncField() } }) heart.publish(LifeCycleTypes.ON_FIELD_UNMOUNT, field) @@ -416,6 +447,7 @@ export function createForm( visible, display, computeState, + dataType, useDirty, props }: Exclude): IField { @@ -430,6 +462,7 @@ export function createForm( nodePath, dataPath, computeState, + dataType, useDirty: isValid(useDirty) ? useDirty : options.useDirty }) field.subscription = { @@ -780,7 +813,10 @@ export function createForm( return arr }, validate(opts?: IFormExtendedValidateFieldOptions) { - return validate(field.getSourceState(state => state.path), opts) + return validate( + field.getSourceState(state => state.path), + opts + ) } } } @@ -1041,9 +1077,8 @@ export function createForm( function userUpdating(field: IField | IVirtualField, fn?: () => void) { if (!field) return - const nodePath = field.getSourceState(state => state.path) - if (nodePath) - env.userUpdateFields.push(field.getSourceState(state => state.path)) + const nodePath = field.state.path + if (nodePath) env.userUpdateFields.push(nodePath) if (isFn(fn)) { fn() } @@ -1223,7 +1258,7 @@ export function createForm( userUpdateFields: [], taskIndexes: {}, removeNodes: {}, - visiblePendingFields: {}, + hiddenPendingFields: {}, lastShownStates: {}, submittingTask: undefined } diff --git a/packages/core/src/shared/model.ts b/packages/core/src/shared/model.ts index 86ce4c81559..21f90aff9cf 100644 --- a/packages/core/src/shared/model.ts +++ b/packages/core/src/shared/model.ts @@ -7,7 +7,8 @@ import { Subscribable, FormPath, FormPathPattern, - isValid + isValid, + toArr } from '@uform/shared' import produce, { Draft, setAutoFreeze } from 'immer' import { @@ -21,6 +22,18 @@ const hasProxy = !!globalThisPolyfill.Proxy setAutoFreeze(false) +const defaults = (...args:any[]):any=>{ + const result = {} + each(args,(target)=>{ + each(target,(value,key)=>{ + if(isValid(value)){ + result[key] = value + } + }) + }) + return result +} + export const createStateModel = ( Factory: IStateModelFactory ): IStateModelProvider => { @@ -32,6 +45,7 @@ export const createStateModel = ( useDirty?: boolean computeState?: (draft: State, prevState: State) => void } + public cacheProps?: any public displayName?: string public dirtyNum: number public dirtys: StateDirtyMap @@ -44,10 +58,7 @@ export const createStateModel = ( super() this.state = { ...Factory.defaultState } this.prevState = { ...Factory.defaultState } - this.props = { - ...Factory.defaultProps, - ...defaultProps - } + this.props = defaults(Factory.defaultProps,defaultProps) this.dirtys = {} this.dirtyNum = 0 this.stackCount = 0 @@ -100,6 +111,36 @@ export const createStateModel = ( } } + watchProps = ( + props: T, + keys: string[], + callback: ( + changedProps: { + [key: string]: any + }, + props?: T + ) => void + ) => { + if (!this.cacheProps) { + this.cacheProps = { ...props } + } else { + let changeNum = 0 + let changedProps = {} + toArr(keys).forEach((key: string) => { + if (!isEqual(this.cacheProps[key], props[key])) { + changeNum++ + changedProps[key] = props[key] + } + }) + if (changeNum > 0) { + if (isFn(callback)) { + callback(changedProps, props) + } + this.cacheProps = { ...props } + } + } + } + setState = ( callback: (state: State | Draft) => State | void, silent = false diff --git a/packages/core/src/state/field.ts b/packages/core/src/state/field.ts index 4ebfe7dbe49..cc624b6b42d 100644 --- a/packages/core/src/state/field.ts +++ b/packages/core/src/state/field.ts @@ -10,6 +10,7 @@ export const FieldState = createStateModel( static defaultState = { name: '', path: '', + dataType: 'any', initialized: false, pristine: true, valid: true, @@ -42,7 +43,8 @@ export const FieldState = createStateModel( } static defaultProps = { - path: '' + path: '', + dataType: 'any' } private state: IFieldState @@ -57,6 +59,7 @@ export const FieldState = createStateModel( this.dataPath = FormPath.getPath(props.dataPath) this.state.name = this.dataPath.entire this.state.path = this.nodePath.entire + this.state.dataType = props.dataType || 'any' } readValues({ value, values }: IFieldStateProps) { @@ -68,9 +71,17 @@ export const FieldState = createStateModel( values = toArr(value) } } + + values = toArr(values) + + if(/array/ig.test(this.state.dataType)){ + value = toArr(value) + values[0] = toArr(values[0]) + } + return { value, - values: toArr(values) + values } } diff --git a/packages/core/src/state/virtual-field.ts b/packages/core/src/state/virtual-field.ts index 4940529d2b4..766511fe4de 100644 --- a/packages/core/src/state/virtual-field.ts +++ b/packages/core/src/state/virtual-field.ts @@ -45,13 +45,13 @@ export const VirtualFieldState = createStateModel< } computeState(draft: IVirtualFieldState, prevState: IVirtualFieldState) { - if (draft.mounted === true) { + if (draft.mounted === true && draft.mounted !== prevState.mounted) { draft.unmounted = false } if (!isValid(draft.props)) { draft.props = prevState.props } - if (draft.unmounted === true) { + if (draft.unmounted === true && draft.unmounted !== prevState.unmounted) { draft.mounted = false } } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 7a6a6006df0..90eb00494f6 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -120,6 +120,7 @@ export interface IStateModelProvider { export interface IFieldState { displayName?: string + dataType: string name: string path: string initialized: boolean @@ -159,6 +160,7 @@ export interface IFieldStateProps { path?: FormPathPattern nodePath?: FormPathPattern dataPath?: FormPathPattern + dataType?: string name?: string value?: any values?: any[] @@ -170,6 +172,7 @@ export interface IFieldStateProps { visible?: boolean display?: boolean useDirty?: boolean + useListMode?: boolean computeState?: (draft: IFieldState, prevState: IFieldState) => void } @@ -322,6 +325,16 @@ export interface IModel extends Subscribable { setState: (callback?: (state: S | Draft) => void, silent?: boolean) => void getSourceState: (callback?: (state: S) => any) => any setSourceState: (callback?: (state: S) => void) => void + watchProps: ( + props: T, + keys: string[], + callback: ( + changedProps: { + [key: string]: any + }, + props: T + ) => void + ) => void hasChanged: (path?: FormPathPattern) => boolean isDirty: (key?: string) => boolean getDirtyInfo: () => StateDirtyMap diff --git a/packages/react-schema-renderer/src/__tests__/__snapshots__/markup.spec.tsx.snap b/packages/react-schema-renderer/src/__tests__/__snapshots__/markup.spec.tsx.snap index f50d757b640..4d2761bdc6b 100644 --- a/packages/react-schema-renderer/src/__tests__/__snapshots__/markup.spec.tsx.snap +++ b/packages/react-schema-renderer/src/__tests__/__snapshots__/markup.spec.tsx.snap @@ -26,6 +26,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "string", "display": true, "displayName": "FieldState", "editable": true, @@ -121,6 +122,7 @@ Object { }, "NO_NAME_FIELD_$0.aa": Object { "active": false, + "dataType": "string", "display": true, "displayName": "FieldState", "editable": true, diff --git a/packages/react-schema-renderer/src/__tests__/__snapshots__/register.spec.tsx.snap b/packages/react-schema-renderer/src/__tests__/__snapshots__/register.spec.tsx.snap index 07b148a81f1..2b550838fb5 100644 --- a/packages/react-schema-renderer/src/__tests__/__snapshots__/register.spec.tsx.snap +++ b/packages/react-schema-renderer/src/__tests__/__snapshots__/register.spec.tsx.snap @@ -26,6 +26,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "string", "display": true, "displayName": "FieldState", "editable": true, @@ -96,6 +97,7 @@ Object { }, "aa": Object { "active": false, + "dataType": "string", "display": true, "displayName": "FieldState", "editable": true, @@ -181,6 +183,7 @@ Object { }, "cc.aa": Object { "active": false, + "dataType": "string", "display": true, "displayName": "FieldState", "editable": true, diff --git a/packages/react-schema-renderer/src/components/SchemaField.tsx b/packages/react-schema-renderer/src/components/SchemaField.tsx index db457315db8..63a270de8ac 100644 --- a/packages/react-schema-renderer/src/components/SchemaField.tsx +++ b/packages/react-schema-renderer/src/components/SchemaField.tsx @@ -60,6 +60,7 @@ export const SchemaField: React.FunctionComponent = ( path={path} initialValue={fieldSchema.default} props={fieldSchema.getSelfProps()} + dataType={fieldSchema.type} triggerType={fieldSchema.getExtendsTriggerType()} editable={fieldSchema.getExtendsEditable()} visible={fieldSchema.getExtendsVisible()} diff --git a/packages/react/src/components/FieldList.tsx b/packages/react/src/components/FieldList.tsx new file mode 100644 index 00000000000..59c51c08be2 --- /dev/null +++ b/packages/react/src/components/FieldList.tsx @@ -0,0 +1,17 @@ +import React from 'react' +import { IFieldStateUIProps } from '../types' +import { Field } from './Field' + +export const FieldList: React.FC = props => { + return React.createElement(Field, { + ...props, + dataType: 'array' + }) +} + +FieldList.displayName = 'ReactInternalFieldList' + +FieldList.defaultProps = { + path: '', + triggerType: 'onChange' +} diff --git a/packages/react/src/hooks/useDirty.ts b/packages/react/src/hooks/useDirty.ts index 19393003b4a..218556585c5 100644 --- a/packages/react/src/hooks/useDirty.ts +++ b/packages/react/src/hooks/useDirty.ts @@ -2,7 +2,7 @@ import React from 'react' import { isEqual } from '@uform/shared' export const useDirty = (input: any = {}, keys: string[] = []) => { - const ref = React.useRef({ data: {...input}, dirtys: {}, num: 0 }) + const ref = React.useRef({ data: { ...input }, dirtys: {}, num: 0 }) ref.current.num = 0 keys.forEach(key => { if (!isEqual(input[key], ref.current.data[key])) { diff --git a/packages/react/src/hooks/useField.ts b/packages/react/src/hooks/useField.ts index 283360f0c93..364dacf0dee 100644 --- a/packages/react/src/hooks/useField.ts +++ b/packages/react/src/hooks/useField.ts @@ -1,8 +1,7 @@ import { useMemo, useEffect, useRef, useContext } from 'react' -import { each, isFn } from '@uform/shared' +import { isFn } from '@uform/shared' import { IFieldState, IForm, IField, IMutators } from '@uform/core' import { getValueFromEvent } from '../shared' -import { useDirty } from './useDirty' import { useForceUpdate } from './useForceUpdate' import { IFieldHook, IFieldStateUIProps } from '../types' import FormContext from '../context' @@ -30,14 +29,6 @@ const extendMutators = ( export const useField = (options: IFieldStateUIProps): IFieldHook => { const forceUpdate = useForceUpdate() - const dirty = useDirty(options, [ - 'props', - 'rules', - 'required', - 'editable', - 'visible', - 'display' - ]) const ref = useRef<{ field: IField unmounted: boolean @@ -51,6 +42,7 @@ export const useField = (options: IFieldStateUIProps): IFieldHook => { if (!form) { throw new Error('Form object cannot be found from context.') } + const mutators = useMemo(() => { let initialized = false ref.current.field = form.registerField(options) @@ -73,15 +65,16 @@ export const useField = (options: IFieldStateUIProps): IFieldHook => { }, []) useEffect(() => { - if (dirty.num > 0) { - ref.current.field.setState((state: IFieldState) => { - each(dirty.dirtys, (result, key) => { - if (result) { - state[key] = options[key] - } + //考虑到组件被unmount,props diff信息会被销毁,导致diff异常,所以需要代理在一个持久引用上 + ref.current.field.watchProps( + options, + ['props', 'rules', 'required', 'editable', 'visible', 'display'], + (props: any) => { + ref.current.field.setState((state: IFieldState) => { + Object.assign(state, props) }) - }) - } + } + ) }) useEffect(() => { diff --git a/packages/react/src/hooks/useVirtualField.ts b/packages/react/src/hooks/useVirtualField.ts index d1b9b3bbd55..b765b2870cb 100644 --- a/packages/react/src/hooks/useVirtualField.ts +++ b/packages/react/src/hooks/useVirtualField.ts @@ -1,7 +1,10 @@ import { useMemo, useEffect, useRef, useContext } from 'react' -import { each } from '@uform/shared' -import { IVirtualFieldStateProps, IVirtualFieldState, IForm } from '@uform/core' -import { useDirty } from './useDirty' +import { + IVirtualFieldStateProps, + IVirtualFieldState, + IForm, + IVirtualField +} from '@uform/core' import { useForceUpdate } from './useForceUpdate' import { IVirtualFieldHook } from '../types' import FormContext from '../context' @@ -10,10 +13,15 @@ export const useVirtualField = ( options: IVirtualFieldStateProps ): IVirtualFieldHook => { const forceUpdate = useForceUpdate() - const dirty = useDirty(options, ['props']) - const ref = useRef({ + //const dirty = useDirty(options, ['props', 'visible', 'display']) + const ref = useRef<{ + field: IVirtualField + unmounted: boolean + subscriberId: number + }>({ field: null, - unmounted: false + unmounted: false, + subscriberId: null }) const form = useContext(FormContext) if (!form) { @@ -22,7 +30,7 @@ export const useVirtualField = ( useMemo(() => { let initialized = false ref.current.field = form.registerVirtualField(options) - ref.current.field.subscribe(() => { + ref.current.subscriberId = ref.current.field.subscribe(() => { if (ref.current.unmounted) return /** * 同步Field状态只需要forceUpdate一下触发重新渲染,因为字段状态全部代理在uform core内部 @@ -35,15 +43,16 @@ export const useVirtualField = ( }, []) useEffect(() => { - if (dirty.num > 0) { - ref.current.field.setState((state: IVirtualFieldState) => { - each(dirty.dirtys, (result, key) => { - if (result) { - state[key] = options[key] - } + //考虑到组件被unmount,props diff信息会被销毁,导致diff异常,所以需要代理在一个持久引用上 + ref.current.field.watchProps( + options, + ['props', 'visible', 'display'], + (props: any) => { + ref.current.field.setState((state: IVirtualFieldState) => { + Object.assign(state, props) }) - }) - } + } + ) }) useEffect(() => { @@ -53,7 +62,7 @@ export const useVirtualField = ( ref.current.unmounted = false return () => { ref.current.unmounted = true - ref.current.field.unsubscribe() + ref.current.field.unsubscribe(ref.current.subscriberId) ref.current.field.setState((state: IVirtualFieldState) => { state.unmounted = true }) //must notify,need to trigger remove value @@ -61,6 +70,7 @@ export const useVirtualField = ( }, []) const state = ref.current.field.getState() + return { state, field: ref.current.field, diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 9c16f2c709b..c5804f52e1d 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -7,6 +7,7 @@ import { export * from '@uform/core' export * from './components/Form' export * from './components/Field' +export * from './components/FieldList' export * from './components/VirtualField' export * from './components/FormSpy' export * from './components/FormProvider' diff --git a/packages/shared/src/isEmpty.ts b/packages/shared/src/isEmpty.ts index 68c6eee8a64..d423619b0b9 100644 --- a/packages/shared/src/isEmpty.ts +++ b/packages/shared/src/isEmpty.ts @@ -3,7 +3,7 @@ const has = Object.prototype.hasOwnProperty const toString = Object.prototype.toString -export const isValid = (val: any) => val !== undefined +export const isValid = (val: any) => val !== undefined && val !== null export function isEmpty(val: any): boolean { // Null and Undefined...