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

React Hook Form中非提交按钮点击致组件多次重渲染求助

问题解决:React Hook Form表单内按钮点击导致多次重渲染

问题现象

点击表单内的「Add banner to list」按钮时,组件频繁重渲染(控制台打印boo约20次);将按钮移到表单外则无此问题,但业务需要在按钮点击时调用setBannersDataList且必须放在表单内。

代码复现

'use client';

import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Button } from './ui/button';
import { SubmitHandler, useForm } from 'react-hook-form';
import { Form, FormControl, FormField, FormItem, FormLabel } from './ui/form';
import { useState } from 'react';

type EditorProps = {
  details: { sizes?: string[] };
};

export default function Component({ details }: EditorProps) {
  const [bannersDataList, setBannersDataList] = useState<any>([]);
 
  const onSubmit: SubmitHandler<any> = (data) => console.log(data);
  const form = useForm<any>();

  const {
    register,
    setValue,
    handleSubmit,
    watch,
    getValues,
    formState: { errors },
  } = form;

  console.log('boo');

  return (
        <Form {...form}>
          <form className="grid w-full items-start gap-6" onSubmit={handleSubmit(onSubmit)}>
                <FormField
                  control={form.control}
                  name="size"
                  render={({ field }) => (
                    <FormItem>
                      <FormLabel>Sizes</FormLabel>
                      <FormControl>
                        <Select
                          {...field}
                          defaultValue="300x250"
                        >
                          <SelectTrigger id="size" className="items-start [&_[data-description]]:hidden">
                            <SelectValue placeholder="Select a size" />
                          </SelectTrigger>
                          <SelectContent>
                            {details.sizes?.map((size) => (
                              <SelectItem value={size} key={size}>
                                {size}
                              </SelectItem>
                            ))}
                          </SelectContent>
                        </Select>
                      </FormControl>
                    </FormItem>
                  )}
                />
            <Button
              onClick={() => {
               // 点击此处触发多次重渲染
               setBannersDataList(prev => [...prev, getValues()]);
              }}
            >
              Add banner to list
            </Button>
          </form>
        </Form>
  );
}

解决方案

1. 阻止按钮默认表单提交行为

表单内的<button>默认类型是submit,点击会触发表单提交,进而触发React Hook Form内部状态更新,导致多次重渲染。给按钮加上type="button"即可避免:

<Button
  type="button"
  onClick={() => {
    setBannersDataList(prev => [...prev, getValues()]);
  }}
>
  Add banner to list
</Button>

也可以在回调里手动阻止默认行为:

<Button
  onClick={(e) => {
    e.preventDefault();
    setBannersDataList(prev => [...prev, getValues()]);
  }}
>
  Add banner to list
</Button>

2. 优化表单订阅逻辑

当前代码中watch()未指定具体字段,会订阅所有表单字段的变化,任何值变动都会触发组件重渲染。如果不需要监听全部字段,只订阅需要的目标字段:

// 仅监听size字段,而非所有表单值
const size = watch('size');

如果完全不需要watch功能,直接移除该解构项,减少不必要的订阅。

3. 稳定回调函数引用

用useCallback包裹按钮点击回调,避免每次组件渲染都创建新函数实例,防止子组件(Button)不必要的重渲染:

import { useCallback } from 'react';

// ...

const handleAddBanner = useCallback(() => {
  setBannersDataList(prev => [...prev, getValues()]);
}, [getValues]);

// ...

<Button type="button" onClick={handleAddBanner}>
  Add banner to list
</Button>

4. 缓存外部依赖项

如果父组件传递的details每次渲染都是新对象(比如直接在父组件中创建{ sizes: [...] }),会导致当前组件频繁重渲染。用useMemo缓存sizes数组:

import { useMemo } from 'react';

// ...

const sizes = useMemo(() => details.sizes || [], [details.sizes]);

// ...

<SelectContent>
  {sizes.map((size) => (
    <SelectItem value={size} key={size}>
      {size}
    </SelectItem>
  ))}
</SelectContent>

5. 调整useForm初始化配置

显式声明shouldUnregister: false(默认值即为false,显式声明可避免潜在配置问题),避免字段被频繁卸载/注册导致的重渲染:

const form = useForm<any>({
  shouldUnregister: false,
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 21:34:52