如何用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
相关产品推荐
相关产品推荐

