瀏覽代碼

fix: arryaField async setValue not update, #713

pointhalo 2 年之前
父節點
當前提交
a7a0ba1f6f

+ 1 - 1
.vscode/launch.json

@@ -11,7 +11,7 @@
             "runtimeArgs": [
                 "--inspect-brk",
                 "${workspaceRoot}/node_modules/.bin/jest",
-                "${workspaceRoot}/packages/semi-ui/select/", // 需要调试哪个组件,替换文件夹路径即可
+                "${workspaceRoot}/packages/semi-ui/form/", // 需要调试哪个组件,替换文件夹路径即可
                 "--runInBand",
                 // "--coverage",
                 "--silent" // ignore warning such as componentWillReceiveProps will be abondon...

+ 9 - 0
content/start/d2c/index.md

@@ -0,0 +1,9 @@
+---
+category: 开始
+title:  Design to code 设计稿转代码
+icon: doc-theme
+localeCode: zh-CN
+order: 3
+---
+
+## 

+ 233 - 11
cypress/integration/form.spec.js

@@ -9,16 +9,238 @@
  * why use `.then`?
  * @see https://docs.cypress.io/guides/core-concepts/variables-and-aliases#Return-Values
  */
+
+const D2C = { name: 'Semi D2C', role: 'Engineer' };
+const C2D = { name: 'Semi C2D', role: 'Designer' };
+const DSM = { name: 'Semi DSM', role: 'Designer' };
+
 describe('Form', () => {
-    it('formApi-setValue with array field path, 3 -> 2, remove middle line field', () => {
-        cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--use-form-api-set-value-update-array');
-        cy.get(':nth-child(3) > .semi-button').click();
-        // line 1
-        cy.get('[x-field-id="effects[0].name"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '1-name');
-        cy.get('[x-field-id="effects[0].type"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '1-type');
-        // line 2
-        cy.get('[x-field-id="effects[1].name"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '3-name');
-        cy.get('[x-field-id="effects[1].type"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '3-type');
-        // cy.get('body').find('.semi-popover .semi-datepicker').should('have.length', 0);
-    });
+    // it('formApi-setValue with array field path, 3 -> 2, remove middle line field', () => {
+    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--use-form-api-set-value-update-array');
+    //     cy.get(':nth-child(3) > .semi-button').click();
+    //     // line 1
+    //     cy.get('[x-field-id="effects[0].name"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '1-name');
+    //     cy.get('[x-field-id="effects[0].type"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '1-type');
+    //     // line 2
+    //     cy.get('[x-field-id="effects[1].name"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '3-name');
+    //     cy.get('[x-field-id="effects[1].type"] > .semi-form-field-main > .semi-input-wrapper > input').should('have.value', '3-type');
+    //     // cy.get('body').find('.semi-popover .semi-datepicker').should('have.length', 0);
+    // });
+
+    // ❌ 发现了bug
+    // modify
+    // it('Basic usage - modify、add blank row、add withInitValue row', () => {
+    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--basic-array-field-demo');
+    //     cy.get('#data-0-name').type('-new');
+    //     cy.get('#data-0-role').type('-new');
+    //     cy.get('#data-0-name').should('have.value', 'Semi D2C-new');
+    //     cy.get('#data-0-role').should('have.value', 'Engineer-new');
+
+    //     // add blank row
+    //     cy.get('#add').click();
+    //     cy.get('#data-2-name').should('have.value', '');
+    //     cy.get('#data-2-role').should('have.value', '');
+
+    //     // add withInitValue row
+    //     cy.get('#addWithInit').click();
+    //     cy.get('#data-3-name').should('have.value', 'Semi New-3');
+    //     cy.get('#data-3-role').should('have.value', 'Designer');
+    // });
+
+    // it('Basic usage - add withInitValue row、add blank row', () => {
+    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--basic-array-field-demo');
+
+    //     // add withInitValue row
+    //     cy.get('#addWithInit').click();
+    //     cy.get('#data-2-name').should('have.value', 'Semi New-3');
+    //     cy.get('#data-2-role').should('have.value', 'Designer');
+
+    //     // add blank row
+    //     cy.get('#add').click();
+    //     cy.get('#data-3-name').should('have.value', '');
+    //     cy.get('#data-3-role').should('have.value', '');
+    // });
+
+    // // // remove row
+    // it('Remove row - 3 -> 2, remove middle', () => {
+    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--remove-demo');
+    //     cy.get('#data-1 button').click();
+
+    //     cy.get('#data-0-name').should('have.value', D2C.name);
+    //     cy.get('#data-0-role').should('have.value', D2C.role);
+    //     cy.get('#data-1-name').should('have.value', DSM.name);
+    //     cy.get('#data-1-role').should('have.value', DSM.role);
+    // });
+
+    // it('Remove row - 3 -> 2, remove middle, 2 -> 1, remove head', () => {
+    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--remove-demo');
+    //     cy.get('#data-1 button').click();
+    //     cy.get('#data-0-name').should('have.value', D2C.name);
+    //     cy.get('#data-0-role').should('have.value', D2C.role);
+    //     cy.get('#data-1-name').should('have.value', DSM.name);
+    //     cy.get('#data-1-role').should('have.value', DSM.role);
+
+    //     cy.get('#data-0 button').click();
+    //     cy.get('#data-0-name').should('have.value', DSM.name);
+    //     cy.get('#data-0-role').should('have.value', DSM.role);
+    //     cy.get('#data-1').should('not.exist');
+    // });
+
+    // it('Remove row - 3 -> 2, remove head', () => {
+    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--remove-demo');
+    //     cy.get('#data-2').should('exist');
+    //     cy.get('#data-0 button').click();
+    //     cy.get('#data-2').should('not.exist');
+
+    //     cy.get('#data-0-name').should('have.value', C2D.name);
+    //     cy.get('#data-0-role').should('have.value', C2D.role);
+    //     cy.get('#data-1-name').should('have.value', DSM.name);
+    //     cy.get('#data-1-role').should('have.value', DSM.role);
+
+    //     cy.get('#data-0 button').click();
+    //     cy.get('#data-0-name').should('have.value', DSM.name);
+    //     cy.get('#data-0-role').should('have.value', DSM.role);
+    // });
+
+    // it('Remove row - 3 -> 2, remove last', () => {
+    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--remove-demo');
+    //     cy.get('#data-2').should('exist');
+    //     cy.get('#data-2 button').click();
+    //     cy.get('#data-2').should('not.exist');
+
+    //     cy.get('#data-0-name').should('have.value', D2C.name);
+    //     cy.get('#data-0-role').should('have.value', D2C.role);
+    //     cy.get('#data-1-name').should('have.value', C2D.name);
+    //     cy.get('#data-1-role').should('have.value', C2D.role);
+    // });
+
+    // // it('Basic usage - add、remove、reset', () => { });
+
+    // // reset
+    // it('Reset Usage: modify  => reset ', () => {
+    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--reset-demo');
+    //     cy.get('#data-0-name').type('-new');
+    //     cy.get('#data-0-role').type('-new');
+
+    //     cy.get('#data-0-name').should('have.value', `${D2C.name}-new`);
+    //     cy.get('#data-0-role').should('have.value', `${D2C.role}-new`);
+
+    //     cy.get('button[type=reset]').click();
+    //     cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+    //     cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+    // });
+
+    // it('Reset Usage: length 2 -> 1  => reset ', () => {
+    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--reset-demo');
+    //     cy.get('#data-0 button').click();
+    //     cy.get('#data-1').should('not.exist');
+    //     cy.get('button[type=reset]').click();
+
+    //     // cy.get('#data-1').should('exist');
+    //     cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+    //     cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+    //     cy.get('#data-1-name').should('have.value', `${C2D.name}`);
+    //     cy.get('#data-1-role').should('have.value', `${C2D.role}`);
+    // });
+
+
+    // it('Reset Usage: length 2 -> 0 -> 3  => reset ', () => { 
+    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--reset-demo');
+    //     cy.get('#data-1 button').click();
+    //     cy.get('#data-0 button').click();
+    //     cy.get('#data-0').should('not.exist');
+
+    //     cy.get('#addWithInit').click();
+    //     cy.get('#addWithInit').click();
+    //     cy.get('#addWithInit').click();
+    //     cy.get('#data-2').should('exist');
+
+    //     cy.get('button[type=reset]').click();
+    //     cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+    //     cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+    //     cy.get('#data-1-name').should('have.value', `${C2D.name}`);
+    //     cy.get('#data-1-role').should('have.value', `${C2D.role}`);
+    //     cy.get('#data-2').should('not.exist');
+    // });
+
+    // it('Reset Usage: length 2 -> 0 -> 2  => reset ', () => {
+    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--reset-demo');
+    //     cy.get('#data-1 button').click();
+    //     cy.get('#data-0 button').click();
+    //     cy.get('#data-0').should('not.exist');
+
+    //     cy.get('#addWithInit').click();
+    //     cy.get('#addWithInit').click();
+    //     cy.get('.line').should('have.length', 2);
+
+    //     cy.get('button[type=reset]').click();
+    //     cy.get('#data-2').should('not.exist');
+    //     cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+    //     cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+    //     cy.get('#data-1-name').should('have.value', `${C2D.name}`);
+    //     cy.get('#data-1-role').should('have.value', `${C2D.role}`);
+    // });
+
+    // it('Combine Usage', () => {
+    //     // add -> remove -> modify
+    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--basic-array-field-demo', {
+    //         onBeforeLoad(win) {
+    //             cy.stub(win.console, 'log').as('consoleLog');
+    //         },
+    //     });
+    //     cy.get('#addWithInit').click();
+    //     cy.get('#data-1 button').click();
+    //     cy.get('#data-0-role').type('-0');
+    //     cy.get('#data-0-name').type('-0');
+
+    //     cy.get('#data-0-name').should('have.value', `${D2C.name}-0`);
+    //     cy.get('#data-0-role').should('have.value', `${D2C.role}-0`);
+    //     cy.get('#data-1-name').should('have.value', `Semi New-3`);
+    //     cy.get('#data-1-role').should('have.value', `Designer`);
+    // });
+
+    // it('combine usage-2', () => {
+    //     // add -> remove -> modify -> reset
+    //     cy.visit('http://127.0.0.1:6006/iframe.html?path=/story/form--basic-array-field-demo');
+    //     cy.get('#data-1 button').click();
+    //     cy.get('#data-0-role').type('-0');
+    //     cy.get('#data-0-name').type('-0');
+    //     cy.get('button[type=reset]').click();
+    //     cy.get('#data-0-name').should('have.value', `${D2C.name}`);
+    //     cy.get('#data-0-role').should('have.value', `${D2C.role}`);
+    //     cy.get('#data-1-name').should('have.value', `${C2D.name}`);
+    //     cy.get('#data-1-role').should('have.value', `${C2D.role}`);
+    // });
+
+
+    // it('Init - Form Props initValues、ArrayField initValue、Field initValue', () => { 
+    // // 一个 Form 三个 ArrayField
+    // });
+    // it('Init - combine', () => {});
+
+    // it('sync setValue - modify value, 2 -> 2', () => { });
+    // it('sync setValue - add, 2 -> 3, add first', () => { });
+    // it('sync setValue - add, 2 -> 3, add middle', () => { });
+    // it('sync setValue - add, 2 -> 3, add last', () => { });
+    // it('sync setValue - remove, 3 -> 2, remove first', () => { });
+    // it('sync setValue - remove, 3 -> 2, remove middle', () => { });
+    // it('sync setValue - remove, 3 -> 2, remove last', () => { });
+    // it('sync setValue - remove, 3 -> 0, remove all', () => { });
+
+    // it('sync setValues - modify', () => { });
+    // it('sync setValues - add', () => { });
+    // it('sync setValues - remove', () => { });
+
+    // it('ASync setValues', () => { });
+
+    // it('ASync setValue', () => { });
+    // it('ASync setValue: ', () => { });
+
+
+    // it('2 Nested ArrayField - add/remove', () => { });
+    // it('2 Nested ArrayField - reset', () => { });
+    // it('2 Nested ArrayField - formApi.setValues', () => { });
+    // it('2 Nested ArrayField - formApi.setValue level-1', () => { });
+    // it('2 Nested ArrayField - formApi.setValue level-2', () => { });
+    // it('3 Nested ArrayField - add/remove', () => {});
 });

+ 25 - 15
packages/semi-foundation/form/foundation.ts

@@ -3,7 +3,7 @@ import BaseFoundation from '../base/foundation';
 import * as ObjectUtil from '../utils/object';
 import isPromise from '../utils/isPromise';
 import { isValid } from './utils';
-import { isUndefined, isFunction, toPath } from 'lodash';
+import { isUndefined, isFunction, toPath, merge } from 'lodash';
 import scrollIntoView, { Options as scrollIntoViewOptions } from 'scroll-into-view-if-needed';
 
 import { BaseFormAdapter, FormState, CallOpts, FieldState, FieldStaff, ComponentProps, setValuesConfig, ArrayFieldStaff } from './interface';
@@ -132,11 +132,13 @@ export default class FormFoundation extends BaseFoundation<BaseFormAdapter> {
     }
 
     // in order to slove byted-issue-289
-    registerArrayField(arrayFieldPath: string, val: any): void {
-        this.updateArrayField(arrayFieldPath, {
-            updateKey: new Date().valueOf(),
-            initValue: val
-        });
+    registerArrayField(arrayFieldPath: string, initValue: any): void {
+        //  save initValue of arrayField, will be use when calling rest
+        // this.updateArrayField(arrayFieldPath, {
+        //     updateKey: new Date().valueOf(),
+        //     initValue: initValue,
+        // });
+        this.registeredArrayField.set(arrayFieldPath, { field: arrayFieldPath, initValue: initValue });
     }
 
     unRegisterArrayField(arrayField: string): void {
@@ -147,9 +149,11 @@ export default class FormFoundation extends BaseFoundation<BaseFormAdapter> {
         return this.registeredArrayField.get(arrayField);
     }
 
-    updateArrayField(arrayField: string, updateValue: any): void {
-        const mergeVal = { ...this.registeredArrayField.get(arrayField), ...updateValue };
-        this.registeredArrayField.set(arrayField, mergeVal);
+    updateArrayField(arrayField: string, updateStaff?: Omit<ArrayFieldStaff, 'field'>): void {
+        const arrayFieldStaff = this.getArrayField(arrayField);
+        const mergeStaff = { ...arrayFieldStaff, ...updateStaff };
+        this.registeredArrayField.set(arrayField, mergeStaff);
+        mergeStaff.forceUpdate(mergeStaff?.updateValue);
     }
 
     validate(fieldPaths?: Array<string>): Promise<unknown> {
@@ -337,9 +341,10 @@ export default class FormFoundation extends BaseFoundation<BaseFormAdapter> {
         const arrayFieldPaths = [...this.registeredArrayField.keys()];
         arrayFieldPaths.forEach(path => {
             const arrayFieldState = this.registeredArrayField.get(path);
-            const arrayFieldInitValue = arrayFieldState.initValue;
+            // clone prevent dom unmounted cause initValue lost
+            const arrayFieldInitValue = this._adapter.cloneDeep(arrayFieldState.initValue);
             this.updateStateValue(path, arrayFieldInitValue, { notNotify: true, notUpdate: true });
-            this.updateArrayField(path, { updateKey: new Date().valueOf() });
+            this.updateArrayField(path, { updateValue: arrayFieldInitValue });
         });
     }
 
@@ -397,7 +402,10 @@ export default class FormFoundation extends BaseFoundation<BaseFormAdapter> {
         if (this.registeredArrayField.size) {
             const arrayFieldPaths = [...this.registeredArrayField.keys()];
             arrayFieldPaths.forEach(path => {
-                this.updateArrayField(path, { updateKey: new Date().valueOf() });
+                this.updateArrayField(path, {
+                    // updateKey: new Date().valueOf(),
+                    updateValue: ObjectUtil.get(_values, path)
+                });
             });
         }
         // When isOverride is true, there may be a non-existent field in the values passed in, directly synchronized to formState.values
@@ -524,7 +532,7 @@ export default class FormFoundation extends BaseFoundation<BaseFormAdapter> {
 
                 // If the reset happens to be, then update the updateKey corresponding to ArrayField to render it again
                 if (this.getArrayField(field)) {
-                    this.updateArrayField(field, { updateKey: new Date().valueOf() });
+                    this.updateArrayField(field, { updateValue: newValue });
                 }
             }
         };
@@ -546,7 +554,8 @@ export default class FormFoundation extends BaseFoundation<BaseFormAdapter> {
                     }
                 });
                 if (this.getArrayField(field)) {
-                    this.updateArrayField(field, { updateKey: new Date().valueOf() });
+                    // todo check一下
+                    this.updateArrayField(field);
                 }
             }
         };
@@ -568,7 +577,8 @@ export default class FormFoundation extends BaseFoundation<BaseFormAdapter> {
                     }
                 });
                 if (this.getArrayField(field)) {
-                    this.updateArrayField(field, { updateKey: new Date().valueOf() });
+                    // todo check 一下
+                    this.updateArrayField(field);
                 }
             }
         };

+ 4 - 2
packages/semi-foundation/form/interface.ts

@@ -113,8 +113,10 @@ export interface FieldStaff {
 
 export interface ArrayFieldStaff {
     field: string;
-    updateKey?: string;
-    initValue?: any
+    // updateKey?: string;
+    updateValue?: any;
+    initValue?: any;
+    forceUpdate?: (value: any) => void
 }
 export interface FormUpdaterContextType {
     register: (field: string, fieldState: FieldState, fieldStuff: FieldStaff) => void;

+ 3 - 2
packages/semi-ui/form/__test__/arrayField.test.js

@@ -194,11 +194,12 @@ describe('Form.ArrayField', () => {
         //     expect(line3Type).toEqual(newLine.type);
         //     done();
         // }, 1000);
-    }); // it('ArrayField-form allowEmpty', () => {
+    }); 
+
+    // it('ArrayField-form allowEmpty', () => {
     // });
     // it('formApi setValues rewrite ArrayField', () => {
     // });
     // it('Nested ArrayField', () => {
     // });
-
 });

+ 68 - 0
packages/semi-ui/form/_story/ArrayField/1-basicUsage.jsx

@@ -0,0 +1,68 @@
+import React, { useState, useRef } from 'react';
+import { Form, Col, Row, Button, ArrayField, Space } from '@douyinfe/semi-ui';
+import { IconMinusCircle, IconPlusCircle } from '@douyinfe/semi-icons';
+
+function BasicArrayFieldDemo() {
+    const formRef = useRef();
+    const formInitValues = {
+        data: [
+            { name: 'Semi D2C', role: 'Engineer' },
+            { name: 'Semi C2D', role: 'Designer' },
+        ]
+    };
+
+    const logValues = () => {
+        console.log(formRef.current.formApi.getValues().data);
+    };
+
+    return (
+        <Form ref={formRef} initValues={formInitValues}>
+            <ArrayField field="data">
+                {({ add, addWithInitValue, arrayFields }) => (
+                    <React.Fragment>
+                        <Space>
+                            <Button id='add' onClick={add} icon={<IconPlusCircle />} type="primary">Add</Button>
+                            <Button
+                                id='addWithInit'
+                                onClick={() => addWithInitValue({ name: `Semi New-${arrayFields.length + 1}`, role: 'Designer' })}
+                                icon={<IconPlusCircle />}
+                                type="primary">
+                                Add with row initValue
+                            </Button>
+                        </Space>
+
+                        {
+                            arrayFields.map(({ field, key, remove }, i) => (
+                                <div key={key} style={{ display: 'flex', width: 600 }} id={`data-${i}`} className='line'>
+                                    <Space>
+                                        <Form.Input
+                                            id={`data-${i}-name`}
+                                            field={`${field}[name]`}
+                                            label={`${field}.name`}
+                                            style={{ width: 200 }}
+                                        />
+                                        <Form.Input
+                                            id={`data-${i}-role`}
+                                            field={`${field}[role]`}
+                                            label={`${field}.role`}
+                                            style={{ width: 200 }}
+                                        />
+                                    </Space>
+                                    <Button style={{ margin: "36px 0 0 12px" }} type="danger" icon={<IconMinusCircle/>} onClick={remove}>remove this line</Button>
+                                </div>
+                            ))
+                        }
+                        <Button htmlType='reset'>Reset</Button>
+                    </React.Fragment>
+                )}
+            </ArrayField>
+            <div>
+                <Button onClick={() => logValues()} id='log'>logValues</Button>
+            </div>
+        </Form>
+    );
+}
+
+BasicArrayFieldDemo.storyName = 'ArrayField-Basic Usage';
+
+export default BasicArrayFieldDemo;

+ 59 - 0
packages/semi-ui/form/_story/ArrayField/2-removeUsage.jsx

@@ -0,0 +1,59 @@
+import React, { useState, useRef } from 'react';
+import { Form, Col, Row, Button, ArrayField, Space } from '@douyinfe/semi-ui';
+import { IconMinusCircle, IconPlusCircle } from '@douyinfe/semi-icons';
+
+function RemoveDemo() {
+    const formRef = useRef();
+    const formInitValues = {
+        data: [
+            { name: 'Semi D2C', role: 'Engineer' },
+            { name: 'Semi C2D', role: 'Designer' },
+            { name: 'Semi DSM', role: 'Designer' },
+        ]
+    };
+    return (
+        <Form ref={formRef} initValues={formInitValues}>
+            <ArrayField field="data">
+                {({ add, addWithInitValue, arrayFields }) => (
+                    <React.Fragment>
+                        <Space>
+                            <Button id='add' onClick={add} icon={<IconPlusCircle />} type="primary">Add</Button>
+                            <Button
+                                id='addWithInit'
+                                onClick={() => addWithInitValue({ name: `Semi New-${arrayFields.length + 1}`, role: 'Designer' })}
+                                icon={<IconPlusCircle />}
+                                type="primary">
+                                Add with row initValue
+                            </Button>
+                        </Space>
+                        {
+                            arrayFields.map(({ field, key, remove }, i) => (
+                                <div key={key} style={{ display: 'flex', width: 600 }} id={`data-${i}`} className='line'>
+                                    <Space>
+                                        <Form.Input
+                                            id={`data-${i}-name`}
+                                            field={`${field}[name]`}
+                                            label={`${field}.name`}
+                                            style={{ width: 200 }}
+                                        />
+                                        <Form.Input
+                                            id={`data-${i}-role`}
+                                            field={`${field}[role]`}
+                                            label={`${field}.role`}
+                                            style={{ width: 200 }}
+                                        />
+                                    </Space>
+                                    <Button style={{ margin: "36px 0 0 12px" }} type="danger" icon={<IconMinusCircle/>} onClick={remove}>remove this line</Button>
+                                </div>
+                            ))
+                        }
+                    </React.Fragment>
+                )}
+            </ArrayField>
+        </Form>
+    );
+}
+
+RemoveDemo.storyName = 'ArrayField-Remove Row';
+
+export default RemoveDemo;

+ 60 - 0
packages/semi-ui/form/_story/ArrayField/3-resetUsage.jsx

@@ -0,0 +1,60 @@
+import React, { useState, useRef } from 'react';
+import { Form, Col, Row, Button, ArrayField, Space } from '@douyinfe/semi-ui';
+import { IconMinusCircle, IconPlusCircle } from '@douyinfe/semi-icons';
+
+function ResetDemo() {
+    const formRef = useRef();
+    const formInitValues = {
+        data: [
+            { name: 'Semi D2C', role: 'Engineer' },
+            { name: 'Semi C2D', role: 'Designer' },
+            // { name: 'Semi DSM', role: 'Designer' },
+        ]
+    };
+    return (
+        <Form ref={formRef} initValues={formInitValues}>
+            <ArrayField field="data">
+                {({ add, addWithInitValue, arrayFields }) => (
+                    <React.Fragment>
+                        <Space>
+                            <Button id='add' onClick={add} icon={<IconPlusCircle />} type="primary">Add</Button>
+                            <Button
+                                id='addWithInit'
+                                onClick={() => addWithInitValue({ name: `Semi New-${arrayFields.length + 1}`, role: 'Designer' })}
+                                icon={<IconPlusCircle />}
+                                type="primary">
+                                Add with row initValue
+                            </Button>
+                        </Space>
+                        {
+                            arrayFields.map(({ field, key, remove }, i) => (
+                                <div key={key} style={{ display: 'flex', width: 600 }} id={`data-${i}`} className='line'>
+                                    <Space>
+                                        <Form.Input
+                                            id={`data-${i}-name`}
+                                            field={`${field}[name]`}
+                                            label={`${field}.name`}
+                                            style={{ width: 200 }}
+                                        />
+                                        <Form.Input
+                                            id={`data-${i}-role`}
+                                            field={`${field}[role]`}
+                                            label={`${field}.role`}
+                                            style={{ width: 200 }}
+                                        />
+                                    </Space>
+                                    <Button style={{ margin: "36px 0 0 12px" }} type="danger" icon={<IconMinusCircle/>} onClick={remove}>remove this line</Button>
+                                </div>
+                            ))
+                        }
+                        <Button htmlType='reset'>Reset</Button>
+                    </React.Fragment>
+                )}
+            </ArrayField>
+        </Form>
+    );
+}
+
+ResetDemo.storyName = 'ArrayField-Reset';
+
+export default ResetDemo;

+ 65 - 0
packages/semi-ui/form/_story/ArrayField/4-manualSetUsage.jsx

@@ -0,0 +1,65 @@
+import React, { useState, useRef } from 'react';
+import { Form, Col, Row, Button, ArrayField, Space } from '@douyinfe/semi-ui';
+import { IconMinusCircle, IconPlusCircle } from '@douyinfe/semi-icons';
+
+function ManualSetDemo() {
+    const formRef = useRef();
+    const formInitValues = {
+        data: [
+            { name: 'Semi D2C', role: 'Engineer' },
+            { name: 'Semi C2D', role: 'Designer' },
+            // { name: 'Semi DSM', role: 'Designer' },
+        ]
+    };
+
+    const setValue = () => {
+        const formApi = formRef.current.formApi;
+    };
+    
+    return (
+        <Form ref={formRef} initValues={formInitValues}>
+            <ArrayField field="data">
+                {({ add, addWithInitValue, arrayFields }) => (
+                    <React.Fragment>
+                        <Space>
+                            <Button id='add' onClick={add} icon={<IconPlusCircle />} type="primary">Add</Button>
+                            <Button
+                                id='addWithInit'
+                                onClick={() => addWithInitValue({ name: `Semi New-${arrayFields.length + 1}`, role: 'Designer' })}
+                                icon={<IconPlusCircle />}
+                                type="primary">
+                                Add with row initValue
+                            </Button>
+                        </Space>
+                        {
+                            arrayFields.map(({ field, key, remove }, i) => (
+                                <div key={key} style={{ display: 'flex', width: 600 }} id={`data-${i}`} className='line'>
+                                    <Space>
+                                        <Form.Input
+                                            id={`data-${i}-name`}
+                                            field={`${field}[name]`}
+                                            label={`${field}.name`}
+                                            style={{ width: 200 }}
+                                        />
+                                        <Form.Input
+                                            id={`data-${i}-role`}
+                                            field={`${field}[role]`}
+                                            label={`${field}.role`}
+                                            style={{ width: 200 }}
+                                        />
+                                    </Space>
+                                    <Button style={{ margin: "36px 0 0 12px" }} type="danger" icon={<IconMinusCircle/>} onClick={remove}>remove this line</Button>
+                                </div>
+                            ))
+                        }
+                        <Button htmlType='reset'>Reset</Button>
+                    </React.Fragment>
+                )}
+            </ArrayField>
+        </Form>
+    );
+}
+
+ManualSetDemo.storyName = 'ArrayField-ManualSet';
+
+export default ManualSetDemo;

+ 4 - 0
packages/semi-ui/form/_story/ArrayField/index.js

@@ -0,0 +1,4 @@
+export { default as BasicArrayFieldDemo } from './1-basicUsage.jsx';
+export { default as RemoveDemo } from './2-removeUsage.jsx';
+export { default as ResetDemo } from './3-resetUsage.jsx';
+export { default as ManualSetDemo } from './4-manualSetUsage.jsx';

+ 0 - 0
packages/semi-ui/form/_story/ArrayField/initUsage.jsx


+ 91 - 8
packages/semi-ui/form/_story/DynamicField/arrayFieldDemo.jsx

@@ -1,6 +1,5 @@
 /* eslint-disable */
 import React, { useState, useLayoutEffect, Component } from 'react';
-import { storiesOf } from '@storybook/react';
 import { Button, Modal, TreeSelect, Row, Col, Avatar, Select as BasicSelect,
     Form,
     useFormState,
@@ -14,8 +13,9 @@ import { Button, Modal, TreeSelect, Row, Col, Avatar, Select as BasicSelect,
     AutoComplete,
     Collapse,
     Space,
+    TextArea,
     Icon } from '../../../index';
-
+import { IconPlusCircle, IconMinusCircle, } from '@douyinfe/semi-icons';
 
 import { ComponentUsingFormState } from '../Hook/hookDemo';
 const { Input, Select, DatePicker, Switch, Slider, CheckboxGroup, Checkbox, RadioGroup, Radio, TimePicker, InputNumber, InputGroup } = Form;
@@ -328,6 +328,7 @@ class ArrayFieldDemo extends React.Component {
 }
 
 
+// Fix 1409
 class ArrayFieldSetValues extends React.Component {
     constructor() {
         super();
@@ -339,11 +340,10 @@ class ArrayFieldSetValues extends React.Component {
     }
 
     componentDidMount() {
-        debugger
         this.formApi.setValues({
             effects: [
-                { name: "脸部贴纸", type: "2D" },
-                { name: "前景贴纸", type: "3D" },
+                { name: "Semi D2C", role: "Engineer" },
+                { name: "Semi DSM", role: "Designer" },
             ],
             test: 'fff'
         })
@@ -374,8 +374,8 @@ class ArrayFieldSetValues extends React.Component {
                                                     // initValue='test'
                                                 />
                                                 <Input
-                                                    field={`${field}[time]`}
-                                                    label={`${field}.time`}
+                                                    field={`${field}[role]`}
+                                                    label={`${field}.role`}
                                                 />
                                                 <Button type="danger" onClick={remove}>remove</Button>
                                             </div>
@@ -398,4 +398,87 @@ class ArrayFieldSetValues extends React.Component {
     }
 }
 
-export { ArrayFieldCollapseDemo, ArrayFieldDemo, ArrayFieldWithFormInitValues, ArrayFieldWithInitValue, ArrayFieldSetValues };
+
+
+class AsyncSetArrayField extends React.Component {
+    constructor() {
+        super();
+        this.state = {
+            data: [
+                { name: 'Semi D2C', role: 'Engineer' },
+                { name: 'Semi C2D', role: 'Designer' },
+            ]
+        };
+    }
+
+    getFormApi = (formApi) => {
+        this.formApi = formApi;
+    }
+
+    change = () => {
+        let rules = this.formApi.getValue('rules');
+        if (!rules) {
+            efferulests = [];
+        }
+        rules.push({ name: `Semi ${new Date().valueOf()}`, role: 'Designer', key: rules.length + 1  });
+        setTimeout(() => {
+            this.formApi.setValue('rules', rules);
+        }, 300);
+   }
+
+    render() {
+        let { data } = this.state;
+        const ComponentUsingFormState = () => {
+            const formState = useFormState();
+            return (
+                <TextArea style={{ marginTop: 10 }} value={JSON.stringify(formState)} />
+            );
+        };
+        return (
+            <Form style={{ width: 800 }} labelPosition='left' labelWidth='100px' allowEmpty getFormApi={this.getFormApi}>
+                <ArrayField field='rules' initValue={data}>
+                    {({ add, arrayFields, addWithInitValue }) => (
+                        <React.Fragment>
+                            <Button onClick={this.change} theme='light'>change</Button>
+                            <Button onClick={add} icon={<IconPlusCircle />} theme='light'>Add new line</Button>
+                            <Button icon={<IconPlusCircle />} onClick={() => {addWithInitValue({ name: `Semi DSM ${arrayFields.length + 1}`, role: 'Designer' });}} style={{ marginLeft: 8 }}>Add new line with init value</Button>
+                            {
+                                arrayFields.map(({ field, key, remove }, i) => (
+                                    <div key={key} style={{ width: 1000, display: 'flex' }}>
+                                        <Form.Input
+                                            field={`${field}[name]`}
+                                            label={`${field}.name`}
+                                            style={{ width: 200, marginRight: 16 }}
+                                        >
+                                        </Form.Input>
+                                        <Form.Select
+                                            field={`${field}[role]`}
+                                            label={`${field}.role`}
+                                            style={{ width: 120 }}
+                                            optionList={[
+                                                { label: 'Engineer', value: 'Engineer' },
+                                                { label: 'Designer', value: 'Designer' },
+                                            ]}
+                                        >
+                                        </Form.Select>
+                                        <Button
+                                            type='danger'
+                                            theme='borderless'
+                                            icon={<IconMinusCircle />}
+                                            onClick={remove}
+                                            style={{ margin: 12 }}
+                                        />
+                                    </div>
+                                ))
+                            }
+                        </React.Fragment>
+                    )}
+                </ArrayField>
+                <ComponentUsingFormState />
+            </Form>
+        );
+    }
+}
+
+
+export { ArrayFieldCollapseDemo, ArrayFieldDemo, ArrayFieldWithFormInitValues, ArrayFieldWithInitValue, ArrayFieldSetValues, AsyncSetArrayField };

+ 0 - 110
packages/semi-ui/form/_story/Reference/inform.test.js

@@ -1,110 +0,0 @@
-import React from 'react';
-// import {
-//     storiesOf
-// } from '@storybook/react';
-
-import { Form, Text } from 'informed';
-import { TextArea } from '@douyinfe/semi-ui';
-
-// import { Form, asField } from '../src/index';
-// import Input from '../../input/index';
-// import Select from '../../select/index';
-
-// const stories = storiesOf('informed', module);
-
-// // // stories.addDecorator(withKnobs);;
-
-// const validate = value => {
-//     return !value || value.length < 5
-//         ? 'Field must be at least five characters'
-//         : undefined;
-// };
-
-// const ErrorText = asField(({
-//         fieldState,
-//         fieldApi,
-//         ...props
-//     }) => {
-//     const {value} = fieldState;
-//     const {setValue, setTouched} = fieldApi;
-//     const {
-//         onChange,
-//         onBlur,
-//         initialValue,
-//         forwardedRef,
-//         ...rest
-//     } = props;
-//     return (
-//         <React.Fragment>
-//             <Input
-//                 {...rest}
-//                 ref={forwardedRef}
-//                 value={!value && value !== 0 ? '' : value}
-//                 onChange={(value, e) => {
-//                     setValue(e.target.value);
-//                     if (onChange) {
-//                         onChange(e);
-//                     }
-
-//                 }}
-//                 onBlur={e => {
-//                     setTouched();
-//                     if (onBlur) {
-//                         onBlur(e);
-//                     }
-//                 }}
-//                 />
-//             </React.Fragment>
-//     );
-// });
-
-// const FromScratch = () => (
-//     <div>
-//     <Form id='custom-form-2'>
-//       {({formApi, formState}) => (
-//         <React.Fragment>
-//           <label>
-//             First name:
-//             <ErrorText
-//                 field='name'
-//                 validate={validate}
-//                 validateOnChange
-//                 validateOnBlur
-//                 />
-//           </label>
-//           <button type='submit'>Submit</button>
-//         <code>{JSON.stringify(formState)}</code>
-//         </React.Fragment>
-//     )}
-//     </Form>
-//   </div>
-// );
-
-// stories.add('Form施工现场', () => (<FromScratch />));
-
-const submit = values => window.alert(`Form successfully submitted with ${JSON.stringify(values)}`);
-
-const validate = value => {
-    if (!value) {
-        return '不能为空';
-    }
-};
-
-class InformDemo extends React.Component {
-    render() {
-        return (
-            <Form onSubmit={submit}>
-                {({ formState, values }) => (
-                    <>
-                        <Text field="panels[11].name" validateOnBlur />
-                        <Text field="panels[11].type" validateOnBlur />
-                        <button type="submit">Submit</button>
-                        <TextArea value={JSON.stringify(formState)} />
-                    </>
-                )}
-            </Form>
-        );
-    }
-}
-
-export { InformDemo };

+ 21 - 15
packages/semi-ui/form/_story/form.stories.jsx

@@ -1,9 +1,7 @@
 import React, { useState, useLayoutEffect, useEffect, useRef } from 'react';
 import {
   Button,
-} from '../../index';
-import {
-  Form,
+  Form
 } from '../../index';
 import { BasicDemoWithInit, LinkFieldForm, DifferentDeclareUsage } from './demo';
 const {
@@ -66,6 +64,7 @@ import {
   ArrayFieldWithFormInitValues,
   ArrayFieldWithInitValue,
   ArrayFieldSetValues,
+  AsyncSetArrayField,
 } from './DynamicField/arrayFieldDemo';
 import { NestArrayField } from './DynamicField/nestArrayField';
 import { ArrayDemo } from './FormApi/arrayDemo';
@@ -82,6 +81,7 @@ import { FieldPathWithArrayDemo } from './Debug/bugDemo';
 import ChildDidMount from './Debug/childDidMount';
 export { default as FormSubmit } from './FormSubmit';
 
+export * from './ArrayField';
 
 export default {
   title: 'Form'
@@ -128,9 +128,17 @@ LayoutSlotErrorMessageLabel.story = {
 export const LayoutModalDemo = () => <ModalFormDemo />;
 
 LayoutModalDemo.story = {
-  name: 'Layout- ModalDemo',
+  name: 'Layout-ModalDemo',
+};
+
+
+export const GroupProp = () => <InputGroupDemo />
+
+GroupProp.story = {
+  name: 'Layout-InputGroup Prop - basic'
 };
 
+
 export const FormApiSetValuesOverride = () => <SetValuesDemo />;
 
 FormApiSetValuesOverride.story = {
@@ -199,6 +207,11 @@ ArrayFieldSetValuesDemo.story ={
   name: 'ArrayField-setValues didMount'
 }
 
+export const AsyncSetArrayFieldDemo  = () => <AsyncSetArrayField></AsyncSetArrayField>;
+AsyncSetArrayFieldDemo.story = {
+  name: 'ArrayField-update async'
+}
+
 export const ArrayFieldNestUsage = () => <NestArrayField />;
 
 ArrayFieldNestUsage.story = {
@@ -237,8 +250,8 @@ ValidateUseRules.story = {
 
 
 export const RaceAsync = () => <RaceAsyncDemo />;
-RaceAsyncDemo.story = {
-  name: 'Validate - race async'
+RaceAsync.story = {
+  name: 'Validate-race async'
 }
 
 export const HooksUseFormApi = () => <UseFormApiDemo />;
@@ -374,14 +387,6 @@ FieldPropRef.story = {
   name: 'Field Prop-ref',
 };
 
-
-export const GroupProp = () => <InputGroupDemo />
-
-GroupProp.story = {
-  name: 'InputGroup Prop - basic'
-};
-
-
 const InitEmptyStringDemo = () => {
   return (
     <Form allowEmpty>
@@ -444,4 +449,5 @@ export const _ChildDidMount = () => <ChildDidMount />;
 
 _ChildDidMount.story = {
   name: 'child did mount',
-};
+};
+

+ 12 - 0
packages/semi-ui/form/_story/form.stories.tsx

@@ -2,12 +2,24 @@ import React, { FunctionComponent } from 'react';
 import { storiesOf } from '@storybook/react';
 import { Form, useFormState, useFormApi, withField, Input, Button, Upload, withFormApi, withFormState } from '../../index';
 import { values } from 'lodash';
+import { Modal } from '../../modal';
 const stories = storiesOf('Form', module);
 import { FormApiContext } from '../context';
 
 
 import type { FormApi, FormFCChild, FormState } from '../interface';
 
+
+
+const ModalForm = <Values extends Record<string, any> = any> (props) => {
+    return <div>
+        <Form<Values>>
+
+        </Form>
+    </div>
+};
+
+
 const treeData = [
     {
         label: '浙江省',

+ 76 - 33
packages/semi-ui/form/arrayField.tsx

@@ -1,7 +1,7 @@
 /* eslint-disable react/destructuring-assignment */
 import React, { Component } from 'react';
 import { getUuidv4 } from '@douyinfe/semi-foundation/utils/uuid';
-import { cloneDeep, isUndefined } from 'lodash';
+import { cloneDeep, isUndefined, isEqual, get } from 'lodash';
 import { FormUpdaterContext, ArrayFieldContext } from './context';
 import warning from '@douyinfe/semi-foundation/utils/warning';
 import type { ArrayFieldStaff, FormUpdaterContextType } from '@douyinfe/semi-foundation/form/interface';
@@ -30,15 +30,16 @@ const filterArrayByIndex = (array: any[], index: number) => array.filter((item,
 
 const getUuidByArray = (array: any[]) => array.map(() => getUuidv4());
 
-const getUpdateKey = (arrayField: ArrayFieldStaff): string | undefined => {
-    if (!arrayField) {
-        return undefined;
-    }
-    if (arrayField && arrayField.updateKey) {
-        return arrayField.updateKey;
-    }
-    return undefined;
-};
+// const getUpdateKey = (arrayField: ArrayFieldStaff): string | undefined => {
+//     if (!arrayField) {
+//         return undefined;
+//     }
+//     // TODO
+//     if (arrayField && arrayField.updateKey) {
+//         return arrayField.updateKey;
+//     }
+//     return undefined;
+// };
 
 const initValueAdapter = (initValue: any) => {
     const iv: any[] = [];
@@ -59,20 +60,28 @@ const initValueAdapter = (initValue: any) => {
  * @param {string[]} oldKeys
  * @returns string[]
  */
-const generateKeys = (value: any[], oldKeys?: string[]) => {
+const generateKeys = (value : any[] = [], oldKeys?: string[], cacheValue: any[] = []) => {
     const val = initValueAdapter(value);
     const newKeys = getUuidByArray(val);
-    // return newKeys;
-    const keys = newKeys.map((key, i) => (oldKeys && oldKeys[i] ? oldKeys[i] : key));
+    const keys = [];
+    value.forEach((newRow, i) => {
+        const cacheRow = get(cacheValue, i);
+        if (!isEqual(newRow, cacheRow)) {
+            keys[i] = newKeys[i];
+        } else {
+            keys[i] = oldKeys && oldKeys[i] ? oldKeys[i] : newKeys[i];
+        }
+
+    });
     return keys;
 };
 
 class ArrayFieldComponent extends Component<ArrayFieldProps, ArrayFieldState> {
     static contextType = FormUpdaterContext;
 
-    cacheFieldValues: any[];
+    cacheFieldValues: any[] | null;
     shouldUseInitValue: boolean;
-    cacheUpdateKey: string;
+    // cacheUpdateKey: string;
     context: FormUpdaterContextType;
 
     constructor(props: ArrayFieldProps, context: FormUpdaterContextType) {
@@ -88,7 +97,7 @@ class ArrayFieldComponent extends Component<ArrayFieldProps, ArrayFieldState> {
         this.addWithInitValue = this.addWithInitValue.bind(this);
         this.remove = this.remove.bind(this);
         this.cacheFieldValues = null;
-        this.cacheUpdateKey = null;
+        // this.cacheUpdateKey = null;
 
         /*
             If updateKey exists, it means that the arrayField (usually a nested ArrayField not at the first level) is only re-mounted due to setValues,
@@ -101,9 +110,13 @@ class ArrayFieldComponent extends Component<ArrayFieldProps, ArrayFieldState> {
         const initValueCopyForFormState = cloneDeep(initValue);
         const initValueCopyForReset = cloneDeep(initValue);
         context.registerArrayField(field, initValueCopyForReset);
-        // register ArrayField will update state.updateKey to render, So there is no need to execute forceUpdate here
         context.updateStateValue(field, initValueCopyForFormState, { notNotify: true, notUpdate: true });
+    }
 
+    componentDidMount(): void {
+        const { field } = this.props;
+        const updater = this.context;
+        updater.updateArrayField(field, { forceUpdate: this.forceUpdate });
     }
 
     componentWillUnmount() {
@@ -112,29 +125,61 @@ class ArrayFieldComponent extends Component<ArrayFieldProps, ArrayFieldState> {
         updater.unRegisterArrayField(field);
     }
 
-    componentDidUpdate() {
+    // componentDidUpdate() {
+    //     const updater = this.context;
+    //     // const { field } = this.props;
+    //     // const { keys } = this.state;
+    //     // const fieldValues = updater.getValue(field);
+    //     // const updateKey = getUpdateKey(updater.getArrayField(field));
+    //     // // when update form outside, like use formApi.setValue('field', [{newItem1, newItem2}]),  formApi.setValues
+    //     // // re generate keys to update arrayField;
+    //     // if (updateKey !== this.cacheUpdateKey) {
+    //     //     const newKeys = generateKeys(fieldValues, keys);
+    //     //     // eslint-disable-next-line
+    //     //     this.setState({ keys: newKeys });
+    //     //     this.cacheUpdateKey = updateKey;
+    //     //     if (this.cacheUpdateKey !== null) {
+    //     //         this.shouldUseInitValue = false;
+    //     //     }
+    //     // } else {
+    //     //     console.log('not update');
+    //     // }
+    // }
+
+    forceUpdate = (value?: any): void => {
+        // const updater = this.context;
+        // const { field } = this.props;
+        // const { keys } = this.state;
+        // const fieldValues = value ? value : updater.getValue(field);
+        // const updateKey = getUpdateKey(updater.getArrayField(field));
+        // if (updateKey !== this.cacheUpdateKey) {
+        //     const newKeys = generateKeys(fieldValues, keys);
+        //     // eslint-disable-next-line
+        //     this.setState({ keys: newKeys });
+        //     this.cacheUpdateKey = updateKey;
+        //     if (this.cacheUpdateKey !== null) {
+        //         this.shouldUseInitValue = false;
+        //     }
+        // } else {
+        //     // console.log('not update');
+        // }
         const updater = this.context;
         const { field } = this.props;
         const { keys } = this.state;
-        const fieldValues = updater.getValue(field);
-        const updateKey = getUpdateKey(updater.getArrayField(field));
-        // when update form outside, like use formApi.setValue('field', [{newItem1, newItem2}]),  formApi.setValues
-        // re generate keys to update arrayField;
-        if (updateKey !== this.cacheUpdateKey) {
-            const newKeys = generateKeys(fieldValues, keys);
-            // eslint-disable-next-line
-            this.setState({ keys: newKeys });
-            this.cacheUpdateKey = updateKey;
-            if (this.cacheUpdateKey !== null) {
-                this.shouldUseInitValue = false;
-            }
-        }
+        const fieldValues = value ? value : updater.getValue(field);
+        // TODO fieldValues 如果长度相同,keys目前仍会相同,需要为新的
+        const newKeys = generateKeys(fieldValues, keys, this.cacheFieldValues);
+        // eslint-disable-next-line
+        this.setState({ keys: newKeys });
+        this.cacheFieldValues = value;
+        this.shouldUseInitValue = false;
     }
 
     add() {
         const { keys } = this.state;
         keys.push(getUuidv4());
         this.shouldUseInitValue = true;
+        // TODO allowEmpty 为 false 的情况下
         this.setState({ keys });
     }
 
@@ -171,7 +216,6 @@ class ArrayFieldComponent extends Component<ArrayFieldProps, ArrayFieldState> {
             newArrayFieldValue.splice(i, 1);
             updater.updateStateValue(field, newArrayFieldValue);
         }
-
         this.setState({ keys: newKeys });
     }
 
@@ -179,7 +223,6 @@ class ArrayFieldComponent extends Component<ArrayFieldProps, ArrayFieldState> {
         const { children, field } = this.props;
         const { keys } = this.state;
         const arrayFields = keys.map((key, i) => ({
-            // key: i,
             key,
             field: `${field}[${i}]`,
             remove: () => this.remove(i),

+ 2 - 0
packages/semi-ui/form/hoc/withField.tsx

@@ -28,6 +28,8 @@ const useIsomorphicEffect = typeof window !== 'undefined' ? useLayoutEffect : us
  * 1. Takes over the value and onChange of the component and synchronizes them to Form Foundation
  * 2. Insert <Label>
  * 3. Insert <ErrorMessage>
+ * 4. Insert extraText
+ * 5. Add A11y Aria Support
  */
 
 function withField<