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

React Hook Form中使用watch()与下拉选择器的性能问题

React Hook Form + MUI Autocomplete动态表单重复校验性能优化

问题场景

在React应用中使用React Hook Form(RHF)处理动态多行表单,每行包含Material-UI的Autocomplete组件,需要校验多行间的Autocomplete选项是否重复。当前通过watch()或getValues()在onValueChange中检查重复,功能正常但引发严重性能问题:修改下拉值后表单响应迟缓,其他输入控件出现明显延迟。

性能瓶颈分析

  1. 全局监听导致过度重渲染:watch('goals')会监听整个数组的所有字段变化,每次调用都会触发组件重渲染,多行场景下重渲染成本指数级上升。
  2. 重复计算无缓存:每次onValueChange都遍历完整的goals数组查找重复,没有缓存已选中值,重复计算消耗大量资源。
  3. Controller渲染逻辑未优化:Controller的render函数内的逻辑未做缓存,状态变化时会重复执行不必要的计算(比如findTerritory)。

优化方案

1. 用useWatch替代全局watch,精准监听

useWatch是RHF提供的Hook,能精准监听指定字段,且返回稳定的引用,避免不必要的重渲染。

import { useWatch } from 'react-hook-form';

// 在组件内部使用
const goals = useWatch({
  control,
  name: 'goals',
  defaultValue: [],
});

2. 缓存已选中值集合,减少遍历开销

用useMemo缓存已选中的地区值集合,每次校验只需检查当前值是否在集合中,无需遍历整个数组:

import { useMemo } from 'react';

const selectedTerritories = useMemo(() => {
  return new Set(goals?.map(item => item[GoalInputs.TERRITORY]).filter(Boolean));
}, [goals]);

3. 优化Autocomplete的渲染逻辑

将Autocomplete的处理逻辑抽成独立组件,用React.memo包裹,避免父组件重渲染时子组件重复渲染:

import React from 'react';

const TerritoryAutocomplete = React.memo(({ field, territoryOptions, error, onChange, selectedTerritories, showError }) => {
  const findTerritory = territoryOptions?.find(({ value }) => value === field?.value);
  
  const handleValueChange = (_, value) => {
    const selectedValue = value?.value;
    if (selectedValue && selectedTerritories.has(selectedValue)) {
      showError({ message: 'This territory is already selected' });
      onChange('');
    } else {
      onChange(selectedValue);
    }
  };

  return (
    <SingleSelect
      {...field}
      fullWidth
      options={territoryOptions}
      value={findTerritory || null}
      onValueChange={handleValueChange}
      error={!!error?.[GoalInputs.TERRITORY]}
      helperText={error?.[GoalInputs.TERRITORY]?.message}
      sx={{ minWidth: 200 }}
    />
  );
});

4. 推荐:将重复校验移到RHF/Yup校验层(更优雅)

把重复校验逻辑从UI的onChange移到表单校验规则中,分离UI和校验逻辑,同时RHF的校验是按需触发的,性能更优:

Yup校验示例

import * as Yup from 'yup';

const validationSchema = Yup.object().shape({
  goals: Yup.array().of(
    Yup.object().shape({
      [GoalInputs.TERRITORY]: Yup.string()
        .test(
          'unique-territory',
          'This territory is already selected',
          function(value) {
            // 拿到当前数组的所有值
            const goals = this.parent;
            // 统计当前value出现的次数
            const count = goals.filter(item => item[GoalInputs.TERRITORY] === value).length;
            // 只允许出现一次
            return count <= 1;
          }
        )
    })
  )
});

// 在useForm中传入validationSchema
const { control, formState: { errors } } = useForm({
  validationSchema,
  defaultValues: { goals: [] }
});

优化后的Controller代码

此时无需在onValueChange中处理校验,直接正常赋值即可:

<Controller
  control={control}
  name={`goals.${index}.${GoalInputs.TERRITORY}`}
  render={({ field: { onChange, value, ...field } }) => {
    const findTerritory = territoryOptions?.find(({ optionValue }) => optionValue === value);
    return (
      <SingleSelect
        {...field}
        fullWidth
        options={territoryOptions}
        value={findTerritory || null}
        onValueChange={(_, option) => onChange(option?.value)}
        error={!!errors?.goals?.[index]?.[GoalInputs.TERRITORY]}
        helperText={errors?.goals?.[index]?.[GoalInputs.TERRITORY]?.message}
        sx={{ minWidth: 200 }}
      />
    );
  }}
/>

最终效果

通过以上优化,既能实现多行Autocomplete的重复选项校验,又能避免过度重渲染和重复计算,保持表单的快速响应。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 01:19:53