嵌套对象场景下使用final-form-array的技术问题咨询
解决嵌套网格的FieldArray批量删除与Yup验证问题
我之前也碰到过类似的嵌套表单场景,你的问题核心是没用到嵌套FieldArray来适配你的双层数组结构——document是数组(对应多个网格),每个数组元素里又有rows数组(对应网格内的行)。FieldArray完全支持这种嵌套场景,只是你之前固定了document[0]只处理第一个网格,所以才觉得它只能支持单个节点。结合Yup验证和批量删除需求,这里给你一套可行的实现方案:
核心思路
- 用外层FieldArray管理
document数组,负责添加/删除整个网格 - 用内层FieldArray管理每个网格下的
rows数组,负责行的添加/删除(包括批量操作) - 编写匹配双层数组结构的Yup Schema,实现全局表单验证
- 批量删除行时注意索引排序,避免因索引偏移删错行
完整代码示例
import { useForm, FieldArray, useFieldArray } from 'react-hook-form'; import * as yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; import { AgGridReact } from 'ag-grid-react'; import 'ag-grid-community/styles/ag-grid.css'; import 'ag-grid-community/styles/ag-theme-alpine.css'; // 匹配你的数据结构的Yup验证Schema const formSchema = yup.object().shape({ document: yup.array().of( yup.object().shape({ rows: yup.array().of( yup.object().shape({ make: yup.string().required('请填写品牌') // 可添加其他字段的验证规则 }) ) }) ) }); function GridForm() { const { control, handleSubmit, formState: { errors } } = useForm({ resolver: yupResolver(formSchema), defaultValues: { document: [{ rows: [{ make: "Tesla" }] }] } }); // 保存每个网格的API,用于批量选中行 const gridApis = {}; const onSubmit = (data) => { console.log('提交数据:', data); }; // 批量删除行的工具函数:必须从大到小排序索引,避免删除后索引偏移 const batchRemoveRows = (removeFn, selectedIndexes) => { [...selectedIndexes].sort((a, b) => b - a).forEach(idx => removeFn(idx)); }; return ( <form onSubmit={handleSubmit(onSubmit)} className="form-container"> {/* 外层FieldArray:管理多个网格(document数组) */} <FieldArray control={control} name="document" render={({ fields: gridFields, remove: removeGrid, append: addGrid }) => ( <> {gridFields.map((gridItem, gridIndex) => ( <div key={gridItem.id} className="grid-wrapper"> <h4>网格 {gridIndex + 1}</h4> <button type="button" onClick={() => removeGrid(gridIndex)}>删除当前网格</button> {/* 内层FieldArray:管理当前网格的行(rows数组) */} <FieldArray control={control} name={`document[${gridIndex}].rows`} render={({ fields: rowFields, remove: removeRow, append: addRow }) => ( <> <div className="grid-actions"> <button type="button" onClick={() => addRow({ make: "" })}>新增行</button> <button type="button" onClick={() => { const selectedNodes = gridApis[gridIndex].getSelectedNodes(); const selectedIndexes = selectedNodes.map(node => node.rowIndex); batchRemoveRows(removeRow, selectedIndexes); }} > 删除选中行 </button> </div> {/* AgGrid渲染 */} <div className="ag-theme-alpine" style={{ height: 300, width: '100%' }}> <AgGridReact rowData={rowFields} columnDefs={[ { field: 'make', editable: true, cellEditor: 'agTextCellEditor', // 可结合react-hook-form的register实现单元格实时验证 cellEditorParams: { onValueChange: (params) => { control.register(`document[${gridIndex}].rows[${params.rowIndex}].make`); } } } ]} onGridReady={(params) => { gridApis[gridIndex] = params.api; }} /> </div> {/* 显示当前网格的验证错误 */} {errors.document?.[gridIndex]?.rows && ( <div className="error-message"> {errors.document[gridIndex].rows.map((rowErr, idx) => ( rowErr.make && <p key={idx}>第{idx+1}行:{rowErr.make.message}</p> ))} </div> )} </> )} /> </div> ))} <button type="button" onClick={() => addGrid({ rows: [] })}>新增网格</button> <button type="submit" className="submit-btn">提交表单</button> </> )} /> </form> ); } export default GridForm;
关键细节说明
- 嵌套FieldArray的路径写法:内层FieldArray的name用
document[${gridIndex}].rows,精准定位到当前网格的行数组,这是实现多网格独立管理的核心。 - 批量删除的索引排序:删除行时如果直接按选中的索引顺序删除,前面的索引删除后,后面的行索引会自动前移,导致后续删除操作指向错误的行。所以必须把选中的索引从大到小排序后再执行删除。
- Yup Schema的匹配:用
yup.array().of()嵌套定义,确保每个网格的每一行都能被正确验证,提交时会自动检查所有行的字段规则。 - AgGrid与react-hook-form的结合:可以通过
control.register在单元格编辑时实时绑定字段,配合Yup实现单元格级的验证提示。
内容的提问来源于stack exchange,提问作者blu




