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
相关产品推荐
相关产品推荐

