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

React非受控组件值实时格式化及多输入控件性能优化问询

问题解答

1. 非受控组件实时格式化输入值的方法

可以借助react-hook-form的register回调结合DOM操作实现实时格式化,核心是在输入变化时同步更新DOM显示和表单状态:

import { useForm } from "react-hook-form";

// 价格格式化函数:过滤非数字字符并添加千分位逗号
const formatPrice = (inputValue) => {
  if (!inputValue) return "";
  const rawNumber = inputValue.replace(/\D/g, "");
  return rawNumber.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};

const YourForm = () => {
  const { register, setValue } = useForm();

  return (
    <input
      {...register("price", {
        onChange: (e) => {
          const formattedValue = formatPrice(e.target.value);
          // 直接更新DOM显示(非受控组件特性)
          e.target.value = formattedValue;
          // 更新表单状态,存储无逗号的原始数值,方便后续验证/提交
          setValue("price", formattedValue.replace(/,/g, ""), { shouldDirty: true });
        },
      })}
      type="text"
    />
  );
};

这种方式既保留了非受控组件的性能优势,又能让用户实时看到格式化后的结果,同时表单内部存储的原始数值不影响后续业务逻辑。

2. 批量控制多输入控件的最优方案

推荐使用react-hook-form的useFieldArray配合封装的格式化组件,这是官方针对动态行数表单设计的方案,能避免不必要的重渲染,同时批量管理控件更高效:

步骤1:封装可复用的价格输入组件

把格式化逻辑抽成独立组件,方便批量复用:

import { useFormContext } from "react-hook-form";

const PriceInput = ({ name }) => {
  const { register, setValue } = useFormContext();
  const formatPrice = (inputValue) => {
    if (!inputValue) return "";
    const rawNumber = inputValue.replace(/\D/g, "");
    return rawNumber.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  };

  return (
    <input
      {...register(name, {
        onChange: (e) => {
          const formattedValue = formatPrice(e.target.value);
          e.target.value = formattedValue;
          // 表单存储原始数值
          setValue(name, formattedValue.replace(/,/g, ""), { shouldDirty: true });
        },
        // 提交时自动转成数字类型,配合yup验证
        valueAsNumber: (val) => parseInt(val.replace(/,/g, "") || 0, 10),
      })}
      type="text"
    />
  );
};

步骤2:用useFieldArray管理动态行

useFieldArray只会更新变化的表单行,避免整表重渲染:

import { useForm, FormProvider, useFieldArray } from "react-hook-form";
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";
import PriceInput from "./PriceInput";

// 表单验证规则
const batchSchema = yup.object().shape({
  items: yup.array().of(
    yup.object().shape({
      name: yup.string().required(),
      price: yup.number().positive().required(),
      // 其他10+字段的验证规则
    })
  ),
});

const BatchRegisterForm = () => {
  const formMethods = useForm({
    resolver: yupResolver(batchSchema),
    defaultValues: { items: [] },
  });
  const { control } = formMethods;
  const { fields, append } = useFieldArray({
    control,
    name: "items",
  });

  // 解析Excel后批量添加行数据
  const handleExcelUpload = (parsedData) => {
    parsedData.forEach((row) => {
      append({
        ...row,
        // 把价格转成字符串,方便格式化显示
        price: row.price?.toString() || "",
      });
    });
  };

  const onSubmit = (formData) => {
    // formData.items中的price是数字类型,可直接提交
    console.log("提交数据:", formData);
    // 后续提交逻辑
  };

  return (
    <FormProvider {...formMethods}>
      <form onSubmit={formMethods.handleSubmit(onSubmit)}>
        {/* Excel上传按钮及逻辑 */}
        <button type="button" onClick={() => handleExcelUpload(mockExcelData)}>
          上传Excel
        </button>

        {/* 动态生成的表单行 */}
        {fields.map((item, index) => (
          <div key={item.id} className="form-row">
            <input {...register(`items.${index}.name`)} placeholder="商品名称" />
            <PriceInput name={`items.${index}.price`} />
            {/* 其他10+输入框,用register批量注册 */}
            <input {...register(`items.${index}.category`)} placeholder="分类" />
            {/* ...更多字段 */}
          </div>
        ))}

        <button type="submit">批量提交</button>
      </form>
    </FormProvider>
  );
};

方案优势

  • useFieldArray仅更新变化的表单行,避免整表重渲染,性能更优;
  • 组件化的格式化输入框,批量复用成本低,维护方便;
  • 表单内部存储原始数值,配合yup验证和提交逻辑更顺畅,显示层则展示格式化后的友好值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 04:17:50