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

在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 01:49:54