React输入框在Inertia.js useForm状态更新后失去焦点的问题求助
React输入框在Inertia.js useForm状态更新后失去焦点的问题求助
大家好,我最近在开发自定义表单组件时遇到了一个棘手的问题:用Inertia.js的useForm hook管理表单状态,每次在输入框敲入字符触发状态更新后,输入框就会失去焦点。我已经排查过输入框的key,确认它没有频繁变化,而且观察到每次输入都会触发表单甚至页面的重渲染,这应该就是失焦的根源,但不知道该怎么修复。
先贴一下我的代码:
Form组件代码
type FormField<T> ={ name: keyof T; label: string; type: "text" | "textarea"; group?: string; } interface Props<T extends Record<string, any>> { initialData: T fields: FormField<T>[] } export function Form<T extends Record<string, any>>(props: Props<T>) { const { initialData, fields, formLayoutKey } = props; const { data, setData, errors, patch } = useForm<T>((initialData ?? {}) as T); const onSubmit = (e?: React.FormEvent) => { e?.preventDefault(); console.log(data) } const renderFieldsByGroup = (group: string) => { return fields .filter((f) => f.group === group) .map((field, index) => { // 之前这里硬编码了name,不过不影响失焦问题 const value = data.name const error = errors[field.name as string]; const key = `${index}-${field.name}` switch (field.type) { case 'text': return ( <FieldSet key={key} label={field.label} error={error}> {/* 同样硬编码了"name",但不影响失焦 */} <Input type="text" placeholder={field.label} data={value ?? ''} onChange={e => setData("name", e.target.value)} /> </FieldSet> ); default: return null; } }); }; return ( <form onSubmit={onSubmit}> <FormLayout> <div key="general"> {renderFieldsByGroup("myGroup")} </div> </FormLayout> </form> ); }
Input组件代码
interface Props { type?: string placeholder?: string classes?: string onChange?: (e: any) => void data?: string } export default function Input(props: Props) { const { type = "text", placeholder, classes, onChange, data} = props return ( <input type={type ?? "text"} placeholder={placeholder} className={`${type === "checkbox" ? "checkbox" : "input border"} ${classes}`} onChange={onChange} value={typeof data === "string" || typeof data === "number" ? data : ""} checked={type === "checkbox" ? Boolean(data) : undefined} /> ); }
问题分析&解决方案
经过排查,这个问题的核心原因是每次状态更新时,渲染字段的函数被重新创建,导致子组件(FieldSet、Input)被频繁卸载再挂载,自然就失去了焦点。下面是具体的修复步骤:
1. 用useCallback缓存渲染函数
renderFieldsByGroup在Form组件的每次重渲染中都会被重新定义,这会让它返回的子组件触发重挂载。我们可以用useCallback缓存这个函数,只在依赖项变化时才重新创建:
import { useCallback } from 'react'; // 修改Form组件中的renderFieldsByGroup const renderFieldsByGroup = useCallback((group: string) => { return fields .filter((f) => f.group === group) .map((field, index) => { // 这里要修正硬编码的name,改成动态绑定field.name const value = data[field.name]; const error = errors[field.name as string]; // 把name放在key前面,可读性更好,也更稳定 const key = `${field.name}-${index}`; switch (field.type) { case 'text': return ( <FieldSet key={key} label={field.label} error={error}> <Input type="text" placeholder={field.label} data={value ?? ''} // 同样修正硬编码的字段名,绑定到当前field的name onChange={e => setData(field.name, e.target.value)} /> </FieldSet> ); default: return null; } }); }, [fields, data, errors, setData]); // 传入必要的依赖项
注意:我顺便修正了你代码里硬编码字段名的问题——之前所有输入框都绑定到
name字段,这不仅逻辑错误,也可能加重重渲染的问题,现在改成动态绑定每个field的name。
2. 优化Input组件的稳定性
虽然你的Input是单独导出的,已经比较稳定,但可以优化一下props的处理逻辑,避免不必要的渲染:
export default function Input(props: Props) { const { type = "text", placeholder, classes, onChange, data} = props; // 统一处理输入框的value/checked属性 const inputProps = type === "checkbox" ? { checked: Boolean(data), value: data } : { value: typeof data === "string" || typeof data === "number" ? data : "" }; return ( <input type={type} placeholder={placeholder} className={`${type === "checkbox" ? "checkbox" : "input border"} ${classes}`} onChange={onChange} {...inputProps} /> ); }
3. 可选:用React.memo优化Form组件
如果Form的父组件会频繁重渲染,也会导致Form跟着重渲染。可以用React.memo包裹Form组件,避免不必要的重渲染:
import { memo } from 'react'; export const Form = memo(function Form<T extends Record<string, any>>(props: Props<T>) { // 组件内部代码不变 }) as typeof Form;
注意:如果父组件传递的
initialData或fields是每次渲染都重新创建的对象/数组,memo会失效,这时候需要在父组件用useMemo缓存这些props。
经过这些修复,输入框应该就不会在状态更新时失去焦点了。
内容来源于stack exchange
相关产品推荐
相关产品推荐

