You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

嵌套对象场景下使用final-form-array的技术问题咨询

解决嵌套网格的FieldArray批量删除与Yup验证问题

我之前也碰到过类似的嵌套表单场景,你的问题核心是没用到嵌套FieldArray来适配你的双层数组结构——document是数组(对应多个网格),每个数组元素里又有rows数组(对应网格内的行)。FieldArray完全支持这种嵌套场景,只是你之前固定了document[0]只处理第一个网格,所以才觉得它只能支持单个节点。结合Yup验证和批量删除需求,这里给你一套可行的实现方案:

核心思路

  1. 外层FieldArray管理document数组,负责添加/删除整个网格
  2. 内层FieldArray管理每个网格下的rows数组,负责行的添加/删除(包括批量操作)
  3. 编写匹配双层数组结构的Yup Schema,实现全局表单验证
  4. 批量删除行时注意索引排序,避免因索引偏移删错行

完整代码示例

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;

关键细节说明

  1. 嵌套FieldArray的路径写法:内层FieldArray的name用document[${gridIndex}].rows,精准定位到当前网格的行数组,这是实现多网格独立管理的核心。
  2. 批量删除的索引排序:删除行时如果直接按选中的索引顺序删除,前面的索引删除后,后面的行索引会自动前移,导致后续删除操作指向错误的行。所以必须把选中的索引从大到小排序后再执行删除。
  3. Yup Schema的匹配:用yup.array().of()嵌套定义,确保每个网格的每一行都能被正确验证,提交时会自动检查所有行的字段规则。
  4. AgGrid与react-hook-form的结合:可以通过control.register在单元格编辑时实时绑定字段,配合Yup实现单元格级的验证提示。

内容的提问来源于stack exchange,提问作者blu

火山引擎 最新活动