React中动态添加的表单输入框输入字符时丢失焦点问题排查
问题:React动态生成的输入框输入时丢失焦点
我在实现基于数组长度动态添加表单字段的功能时,遇到了输入框焦点丢失的问题——每次在任意输入框中输入字符,焦点都会立刻消失。以下是我的代码实现:
const [otherSources, setOtherSources] = useState<Source[]>([]) const SourceView = (source: Source & { index: number }) => { return <div> <div className="field"> <label className="label">Name</label> <div className="control"> <input className="input" onChange={(event) => { const copyOtherSources = otherSources const sourceElement = copyOtherSources[source.index] sourceElement.name = event.target.value setOtherSources([...copyOtherSources]) }} value={source.name} /> </div> </div> <div className="field"> <label className="label">Url</label> <div className="control"> <textarea className="textarea" onChange={(event) => { const copyOtherSources = otherSources const sourceElement = copyOtherSources[source.index] sourceElement.url = event.target.value setOtherSources([...copyOtherSources]) }} rows={2} value={source.url} /> </div> </div> </div> } return ( <div className="field"> <label className="label">Other Sources (comma separated)</label> { (otherSources?.length ?? 0) > 0 && otherSources.map((source, index) => { return <SourceView {...source} index={index} key={`field${index}`} /> }) } </div> )
问题原因
- 组件定义位置错误:
SourceView被定义在父组件的渲染逻辑内部,每次父组件因状态更新重渲染时,SourceView都会被重新定义为一个全新的组件类型。 - 组件渲染方式问题:使用
<SourceView />组件标签语法调用时,React会将其识别为独立组件。每次父组件重渲染,新的SourceView组件类型会导致旧组件被卸载、新组件重新挂载,输入框自然会丢失焦点。
修复方案
- 将
SourceView移到父组件外部定义:避免每次父组件重渲染都重新创建组件类型,保证组件的一致性。 - 改用函数调用替代组件标签:直接调用
SourceView()函数生成JSX,这样它会被当作普通的渲染片段,而非独立组件,不会触发卸载重挂载。 - 优化状态更新逻辑:原代码中直接修改状态数组的元素属于可变操作,改为通过不可变方式更新状态,避免潜在的状态不一致问题。
修复后的代码示例:
// 将SourceView移到父组件外部,确保组件类型稳定 const SourceView = (props: { source: Source; index: number; updateSource: (index: number, key: 'name' | 'url', value: string) => void; }) => { const { source, index, updateSource } = props; return ( <div> <div className="field"> <label className="label">Name</label> <div className="control"> <input className="input" onChange={(event) => updateSource(index, 'name', event.target.value)} value={source.name} /> </div> </div> <div className="field"> <label className="label">Url</label> <div className="control"> <textarea className="textarea" onChange={(event) => updateSource(index, 'url', event.target.value)} rows={2} value={source.url} /> </div> </div> </div> ); }; // 父组件 const ParentComponent = () => { const [otherSources, setOtherSources] = useState<Source[]>([]); // 抽离不可变更新逻辑 const updateSource = (index: number, key: 'name' | 'url', value: string) => { setOtherSources((prevSources) => { const newSources = [...prevSources]; // 创建新对象,避免修改原状态元素 newSources[index] = { ...newSources[index], [key]: value }; return newSources; }); }; return ( <div className="field"> <label className="label">Other Sources (comma separated)</label> {otherSources.length > 0 && otherSources.map((source, index) => { // 直接调用函数生成JSX return SourceView({ source, index, updateSource }); })} </div> ); };
内容的提问来源于stack exchange,提问作者Tarun Chawla
相关产品推荐
相关产品推荐

