1- import React , { useEffect , useImperativeHandle , useMemo , useRef , useState } from 'react' ;
2- import { cloneDeep , get , isEqual , merge , set , unset } from 'lodash-es' ;
31import log from '@tdesign/common-js/log/index' ;
2+ import { castArray , cloneDeep , get , isEqual , merge , set , unset } from 'lodash-es' ;
3+ import React , { useEffect , useImperativeHandle , useMemo , useRef , useState } from 'react' ;
44import { FormListContext , useFormContext , useFormListContext } from './FormContext' ;
55import { HOOK_MARK } from './hooks/useForm' ;
6- import { calcFieldValue , concatNamePath , normalizeNamePath } from './utils' ;
6+ import { calcFieldValue , concatName , convertNameToArray , swap } from './utils' ;
77
88import type { FormItemInstance } from './FormItem' ;
99import type { FormListField , FormListFieldOperation , NamePath , TdFormListProps } from './type' ;
@@ -23,8 +23,8 @@ const FormList: React.FC<TdFormListProps> = (props) => {
2323 const { fullPath : parentFullPath , initialData : parentInitialData } = useFormListContext ( ) ;
2424
2525 const fullPath = useMemo ( ( ) => {
26- const normalizedName = normalizeNamePath ( name ) ;
27- const normalizedParentPath = normalizeNamePath ( parentFullPath ) ;
26+ const normalizedName = convertNameToArray ( name ) ;
27+ const normalizedParentPath = convertNameToArray ( parentFullPath ) ;
2828 // 如果没有父路径,直接使用 name
2929 if ( normalizedParentPath . length === 0 ) {
3030 return normalizedName ;
@@ -36,34 +36,36 @@ const FormList: React.FC<TdFormListProps> = (props) => {
3636 normalizedParentPath . every ( ( segment , index ) => normalizedName [ index ] === segment ) ;
3737 if ( isAbsolutePath ) return normalizedName ;
3838 // 如果是相对路径,与父路径拼接
39- return concatNamePath ( parentFullPath , name ) ;
39+ return concatName ( parentFullPath , name ) ;
4040 } , [ parentFullPath , name ] ) ;
4141
4242 const initialData = useMemo ( ( ) => {
4343 let propsInitialData ;
4444 if ( props . initialData ) {
4545 propsInitialData = props . initialData ;
4646 } else if ( parentFullPath && parentInitialData ) {
47- const relativePath = fullPath . slice ( normalizeNamePath ( parentFullPath ) . length ) ;
47+ const relativePath = fullPath . slice ( convertNameToArray ( parentFullPath ) . length ) ;
4848 propsInitialData = get ( parentInitialData , relativePath ) ;
4949 } else {
5050 propsInitialData = get ( initialDataFromForm , fullPath ) ;
5151 }
52- return propsInitialData ;
52+ return cloneDeep ( propsInitialData ) ;
5353 } , [ fullPath , parentFullPath , initialDataFromForm , parentInitialData , props . initialData ] ) ;
5454
5555 const [ formListValue , setFormListValue ] = useState ( ( ) => get ( form ?. store , fullPath ) || initialData || [ ] ) ;
5656 const [ fields , setFields ] = useState < FormListField [ ] > ( ( ) =>
57- formListValue ? .map ( ( data , index ) => ( {
57+ formListValue . map ( ( data , index ) => ( {
5858 data : { ...data } ,
5959 key : ( globalKey += 1 ) ,
6060 name : index ,
6161 isListField : true ,
6262 } ) ) ,
6363 ) ;
6464
65- const formListMapRef = useRef < Map < NamePath , React . RefObject < FormItemInstance > > > ( new Map ( ) ) ;
65+ // 暴露给 Form 的引用当前 FormList 实例
6666 const formListRef = useRef < FormItemInstance > ( null ) ;
67+ // 存储当前 FormList 下所有的 FormItem 实例
68+ const formListMapRef = useRef < Map < NamePath , React . RefObject < FormItemInstance > > > ( new Map ( ) ) ;
6769
6870 const snakeName = [ ]
6971 . concat ( name )
@@ -72,88 +74,56 @@ const FormList: React.FC<TdFormListProps> = (props) => {
7274
7375 const isMounted = useRef ( false ) ;
7476
75- const buildDefaultFieldMap = ( ) => {
76- if ( formListMapRef . current . size <= 0 ) return { } ;
77- const defaultValues : Record < string , any > = { } ;
78- formListMapRef . current . forEach ( ( _ , itemPath ) => {
79- const itemPathArray = normalizeNamePath ( itemPath ) ;
80- if ( itemPathArray . length !== normalizeNamePath ( fullPath ) . length + 2 ) return ;
81- const fieldName = itemPathArray [ itemPathArray . length - 1 ] ;
82- // add 没有传参时,构建一个包含所有子字段名称的对象,用 undefined 作为值,仅用于占位,确保回调给用户的数据结构完整
83- defaultValues [ fieldName ] = undefined ;
84- } ) ;
85- return defaultValues ;
77+ const updateFormList = ( newFields : any , newFormListValue : any ) => {
78+ const normalizedFields = newFields . map ( ( f , index ) => ( {
79+ ...f ,
80+ name : index , // 重新规范 name 索引
81+ } ) ) ;
82+ setFields ( normalizedFields ) ;
83+ setFormListValue ( newFormListValue ) ;
84+ set ( form ?. store , fullPath , newFormListValue ) ;
85+ const changeValue = calcFieldValue ( fullPath , newFormListValue ) ;
86+ onFormItemValueChange ?.( changeValue ) ;
8687 } ;
8788
8889 const operation : FormListFieldOperation = {
8990 add ( defaultValue ?: any , insertIndex ?: number ) {
90- const cloneFields = [ ...fields ] ;
91- const index = insertIndex ?? cloneFields . length ;
92- cloneFields . splice ( index , 0 , {
91+ const newFields = cloneDeep ( fields ) ;
92+ const index = insertIndex ?? newFields . length ;
93+
94+ newFields . splice ( index , 0 , {
9395 key : ( globalKey += 1 ) ,
9496 name : index ,
9597 isListField : true ,
9698 } ) ;
97- cloneFields . forEach ( ( field , index ) => Object . assign ( field , { name : index } ) ) ;
98- setFields ( cloneFields ) ;
99-
100- const nextFormListValue = cloneDeep ( formListValue ) ;
101-
102- let finalValue = defaultValue ;
103- if ( finalValue === undefined ) {
104- finalValue = buildDefaultFieldMap ( ) ;
105- }
106- if ( finalValue ) {
107- nextFormListValue ?. splice ( index , 0 , cloneDeep ( finalValue ) ) ;
108- setFormListValue ( nextFormListValue ) ;
109- }
110-
111- set ( form ?. store , fullPath , nextFormListValue ) ;
112- const newPath = [ ...normalizeNamePath ( fullPath ) , index ] ;
113- const fieldValue = calcFieldValue ( newPath , finalValue ) ;
114- onFormItemValueChange ?.( fieldValue ) ;
99+
100+ const newFormListValue = cloneDeep ( formListValue ) . splice ( index , 0 , cloneDeep ( defaultValue ) ) ;
101+
102+ updateFormList ( newFields , newFormListValue ) ;
115103 } ,
116104 remove ( index : number | number [ ] ) {
117- const indices = Array . isArray ( index ) ? index : [ index ] ;
118-
119- const nextFields = fields
120- . filter ( ( item ) => ! indices . includes ( item . name ) )
121- . map ( ( field , i ) => ( { ...field , name : i } ) ) ;
122- setFields ( nextFields ) ;
123-
124- const nextFormListValue = cloneDeep ( formListValue ) . filter ( ( _ , idx ) => ! indices . includes ( idx ) ) ;
125- setFormListValue ( nextFormListValue ) ;
126- if ( nextFormListValue . length ) {
127- set ( form ?. store , fullPath , nextFormListValue ) ;
128- }
129- const fieldValue = calcFieldValue ( fullPath , nextFormListValue ) ;
130- onFormItemValueChange ?.( fieldValue ) ;
105+ const indices = castArray ( index ) ;
106+
107+ const newFields = fields . filter ( ( f ) => ! indices . includes ( f . name ) ) ;
108+ const newFormListValue = cloneDeep ( formListValue ) . filter ( ( _ , i ) => ! indices . includes ( i ) ) ;
109+
110+ updateFormList ( newFields , newFormListValue ) ;
131111 } ,
112+
132113 move ( from : number , to : number ) {
133- const cloneFields = [ ...fields ] ;
134- const fromItem = { ...cloneFields [ from ] } ;
135- const toItem = { ...cloneFields [ to ] } ;
136- cloneFields [ to ] = fromItem ;
137- cloneFields [ from ] = toItem ;
138- set ( form ?. store , fullPath , [ ] ) ;
139- setFields ( cloneFields ) ;
140- } ,
141- } ;
114+ const newFields = cloneDeep ( fields ) ;
115+ const newFormListValue = cloneDeep ( formListValue ) ;
142116
143- const handleFieldUpdateTasks = ( fieldData : any [ ] , callback : Function ) => {
144- Array . from ( formListMapRef . current . values ( ) ) . forEach ( ( formItemRef ) => {
145- if ( ! formItemRef . current ) return ;
146- const { name : childName } = formItemRef . current ;
147- const data = get ( fieldData , childName ) ;
148- if ( data !== undefined ) callback ( formItemRef , data ) ;
149- } ) ;
150- const fieldValue = calcFieldValue ( fullPath , fieldData ) ;
151- onFormItemValueChange ?.( fieldValue ) ;
117+ swap ( newFields , from , to ) ;
118+ swap ( newFormListValue , from , to ) ;
119+
120+ updateFormList ( newFields , newFormListValue ) ;
121+ } ,
152122 } ;
153123
154124 function setListFields ( fieldData : any [ ] , callback : Function ) {
155- const currList = get ( form ?. store , fullPath ) || [ ] ;
156- if ( isEqual ( currList , fieldData ) ) return ;
125+ const currList = get ( form ?. store , fullPath ) ;
126+ if ( isEqual ( [ currList ] , fieldData ) ) return ;
157127
158128 const newFields = fieldData . map ( ( _ , index ) => {
159129 const currField = fields [ index ] ;
@@ -167,15 +137,19 @@ const FormList: React.FC<TdFormListProps> = (props) => {
167137 } ;
168138 } ) ;
169139
170- setFields ( newFields ) ;
171- set ( form ?. store , fullPath , fieldData ) ;
172- handleFieldUpdateTasks ( fieldData , callback ) ;
140+ Array . from ( formListMapRef . current . values ( ) ) . forEach ( ( formItemRef ) => {
141+ if ( ! formItemRef . current ) return ;
142+ const { name : childName } = formItemRef . current ;
143+ const data = get ( fieldData , childName ) ;
144+ if ( data !== undefined ) callback ( formItemRef , data ) ;
145+ } ) ;
146+
147+ updateFormList ( newFields , fieldData ) ;
173148 }
174149
175150 useEffect ( ( ) => {
176151 if ( ! name || ! formMapRef ) return ;
177152 formMapRef . current . set ( fullPath , formListRef ) ;
178-
179153 return ( ) => {
180154 // eslint-disable-next-line react-hooks/exhaustive-deps
181155 formMapRef . current . delete ( fullPath ) ;
0 commit comments