You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用React Hook Form的useFieldArray()优化分组渲染表单输入?

问题描述

我有如下翻译数组:

const translations = [
  {
    name: 'John',
    description: 'Some description',
    language: 'en',
    slug: 'some slug'
  },
  {
    name: 'John',
    description: 'Some description',
    language: 'fr',
    slug: 'some slug'
  },
  {
    name: 'John',
    description: 'Some description',
    language: 'de',
    slug: 'some slug'
  },
]

需要按以下结构渲染表单:

Name:
 1. English name input
 2. French Name input
 3. German Name input

Description:
 1. English description textarea
 2. French description textarea
 3. German description textarea

目前我通过多次循环fields数组来分组渲染输入框,代码如下:

const { register, control } = useForm<{ translations: Translation[] }>({
  defaultValues: {
    translations: myTranslations
  }
})
const {fields} = useFieldArray({
  control,
  name: 'translations'
})

return (
  <div>
  {
    // 渲染name输入框
    fields.map((field, index) => (
      <input {...register(`translations.${index}.name`)} />
    ))
  }
  {
    // 渲染description输入框
    fields.map((field, index) => (
      <input {...register(`translations.${index}.description`)} />
    ))
  }
  </div>
)

想知道有没有优化方案,只需一次循环fields数组,就能将同类型字段归类到对应分组下渲染?


优化方案

当然可以,核心思路是先通过一次遍历把fields数据按字段类型分组,再基于分组结果渲染对应输入控件,既减少循环次数,也让代码更易维护。

步骤1:定义字段配置

先梳理要渲染的字段信息,把字段名、显示标签、对应组件类型统一管理:

const fieldConfigs = [
  { key: 'name', label: 'Name', component: 'input' },
  { key: 'description', label: 'Description', component: 'textarea' },
];

步骤2:一次循环完成数据分组

用reduce遍历fields,把每个翻译项的对应字段归类到分组对象中:

const groupedFields = fields.reduce((acc, field, index) => {
  fieldConfigs.forEach(({ key }) => {
    // 初始化分组数组
    if (!acc[key]) acc[key] = [];
    // 存入当前字段的索引和语言信息,方便后续渲染
    acc[key].push({ ...field, index });
  });
  return acc;
}, {});

步骤3:按分组渲染表单

基于分组后的对象和字段配置,依次渲染每个字段的多语言输入控件:

return (
  <div>
    {fieldConfigs.map(({ key, label, component }) => (
      <div key={key} className="field-group">
        <h3>{label}:</h3>
        {groupedFields[key].map(({ index, language }) => {
          const InputComponent = component;
          return (
            <div key={index} className="language-item">
              <span>{language.toUpperCase()} {label}</span>
              <InputComponent 
                {...register(`translations.${index}.${key}`)}
                defaultValue={fields[index][key]}
              />
            </div>
          );
        })}
      </div>
    ))}
  </div>
);

完整可运行代码

import { useForm, useFieldArray } from 'react-hook-form';

type Translation = {
  name: string;
  description: string;
  language: string;
  slug: string;
};

const myTranslations = [
  { name: 'John', description: 'Some description', language: 'en', slug: 'some-slug' },
  { name: 'Jean', description: 'Une description', language: 'fr', slug: 'un-slug' },
  { name: 'Johann', description: 'Ein beschreibung', language: 'de', slug: 'ein-slug' },
];

const TranslationsForm = () => {
  const { register, control } = useForm<{ translations: Translation[] }>({
    defaultValues: { translations: myTranslations },
  });
  const { fields } = useFieldArray({ control, name: 'translations' });

  const fieldConfigs = [
    { key: 'name', label: 'Name', component: 'input' },
    { key: 'description', label: 'Description', component: 'textarea' },
  ];

  const groupedFields = fields.reduce((acc, field, index) => {
    fieldConfigs.forEach(({ key }) => {
      acc[key] = [...(acc[key] || []), { ...field, index }];
    });
    return acc;
  }, {} as Record<string, (Translation & { index: number })[]>);

  return (
    <div style={{ display: 'flex', gap: '2rem', padding: '1rem' }}>
      {fieldConfigs.map(({ key, label, component }) => (
        <div key={key} style={{ flex: 1 }}>
          <h3>{label}</h3>
          {groupedFields[key].map(({ index, language }) => {
            const InputComponent = component;
            return (
              <div key={index} style={{ marginBottom: '1rem' }}>
                <p>{language.toUpperCase()}</p>
                <InputComponent
                  {...register(`translations.${index}.${key}`)}
                  style={{ width: '100%', padding: '0.5rem' }}
                />
              </div>
            );
          })}
        </div>
      ))}
    </div>
  );
};

export default TranslationsForm;

方案优势

  • 仅需一次遍历fields数组,避免多次循环带来的性能浪费(翻译项越多效果越明显)
  • 字段配置与渲染逻辑分离,后续新增字段(比如slug)只需在fieldConfigs中添加一行配置,无需修改渲染代码
  • 代码结构更清晰,分组逻辑和渲染逻辑职责明确,维护成本更低

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 00:42:46