在useFieldArray中使用MUI Select组件的性能问题求助
优化方案:MUI Select在react-hook-form useFieldArray中的性能问题解决
核心问题分析
当前实现的卡顿源于两个关键点:
- 每次选择/删除操作触发所有列表项(Select+TextInput)全量重渲染,未做组件级渲染优化
- 100+选项的MUI Select每次渲染会生成大量MenuItem节点,DOM操作成本高;删除第一项时,后续列表项的字段索引变化,导致Controller组件重新挂载,加剧卡顿
具体优化方案
1. 给可复用组件添加React.memo,阻止不必要重渲染
给SelectComponent和TextInputComponent包裹React.memo,同时用useMemo/useCallback确保传递的props引用稳定:
// 优化SelectComponent const SelectComponent = React.memo(({ name, options, control, selectedValues }) => { const { field } = useController({ name, control, defaultValue: '' }); // 缓存过滤后的可用选项,避免每次渲染重复计算 const availableOptions = useMemo(() => { return options.filter(opt => !selectedValues.includes(opt.value)); }, [options, selectedValues]); return ( <Select value={field.value} onChange={(e) => field.onChange(e.target.value)} label="Select" fullWidth > <MenuItem value="">None</MenuItem> {availableOptions.map(opt => ( <MenuItem key={opt.value} value={opt.value}> {opt.label} </MenuItem> ))} </Select> ); }); // 优化TextInputComponent const TextInputComponent = React.memo(({ name, control }) => { const { field } = useController({ name, control, defaultValue: '' }); return ( <TextField value={field.value} onChange={field.onChange} label="Text" fullWidth /> ); });
2. 稳定已选值的引用,避免触发子组件重渲染
在父组件中用useMemo缓存已选选项数组,确保只有字段数组变化时才更新:
// 父组件中 const { control, register, formState: { errors }, getValues } = useForm({ defaultValues: { array: [{ selectValue: '', textValue: '' }] } }); const { fields, append, remove } = useFieldArray({ control, name: 'array' }); // 缓存已选值,避免每次渲染重新生成数组 const selectedValues = useMemo(() => { return fields.map(item => item.selectValue).filter(Boolean); }, [fields]);
3. 优化MUI Select的大量选项渲染(虚拟滚动)
当选项数量超过50时,用虚拟滚动替代全量渲染MenuItem,只渲染可视区域内的选项。推荐使用MUI X的Autocomplete组件,自带虚拟滚动功能:
// 替换Select为带虚拟滚动的Autocomplete const SelectComponent = React.memo(({ name, options, control, selectedValues }) => { const { field } = useController({ name, control, defaultValue: '' }); const availableOptions = useMemo(() => { return options.filter(opt => !selectedValues.includes(opt.value)); }, [options, selectedValues]); return ( <Autocomplete options={availableOptions} getOptionLabel={(opt) => opt.label} value={availableOptions.find(opt => opt.value === field.value) || null} onChange={(_, newValue) => field.onChange(newValue?.value || '')} renderInput={(params) => <TextField {...params} label="Select" fullWidth />} // 开启虚拟滚动(需安装@mui/x-autocomplete) virtualize ListboxProps={{ sx: { maxHeight: 300 } }} /> ); });
4. 减少删除操作导致的重渲染(自定义字段ID)
默认useFieldArray用数组索引作为字段路径(如array[0].selectValue),删除第一项时后续项索引变化,导致Controller的name更新触发重挂载。解决方法是给每个字段项添加唯一ID,用ID作为字段标识:
// 初始化时给每个项添加唯一ID import { v4 as uuidv4 } from 'uuid'; const { control, register, formState: { errors }, getValues } = useForm({ defaultValues: { array: [{ id: uuidv4(), selectValue: '', textValue: '' }] } }); // 添加新项时生成唯一ID const appendItem = () => { append({ id: uuidv4(), selectValue: '', textValue: '' }); }; // 子组件的name绑定ID,避免索引变化导致的重渲染
5. 关闭RHF不必要的状态订阅
在useForm配置中减少不必要的状态监听,降低重渲染频率:
const { control, register, formState: { errors }, getValues } = useForm({ defaultValues: { array: [{ selectValue: '', textValue: '' }] }, shouldUnregister: true, // 移除未挂载字段的状态 mode: 'onSubmit' // 根据业务调整验证模式,避免实时验证触发频繁重渲染 });
验证效果
优化后:
- 选择选项时,仅当前操作的Select组件和父组件重渲染,其他列表项不再触发重渲染
- 100+选项的渲染成本大幅降低,虚拟滚动仅渲染可视区域选项
- 删除第一项时,后续列表项的字段路径不再变化(使用自定义ID时),避免大量Controller组件重挂载
内容的提问来源于stack exchange,提问作者MaxLAB
相关产品推荐
相关产品推荐

