React输入框onChange触发后失去焦点问题求助
问题原因分析
你的输入框失去焦点、整个<tbody>重渲染的核心问题是Row组件定义在IngredientsTable函数内部。每次IngredientsTable的状态(比如ingredients)更新时,组件会重新执行,内部的Row组件会被重新创建。React会把这个新的Row组件视为完全不同的组件类型,因此会卸载旧的表格行,再挂载新的行——这直接导致输入框失去焦点,同时触发整个的重渲染。
另外你的addRow函数还存在闭包问题:更新ingredients时使用的rowsGenerated + 1可能不是最新的状态值,应该通过函数式更新来获取最新计数。
修复方案
方案1:将Row组件移到父组件外部
把Row组件从IngredientsTable内部抽离,避免每次父组件重渲染时重新创建该组件:
import React, { useState } from 'react'; // 将Row组件移到IngredientsTable外部 const Row = ({ rowId, name, proportion, onFieldChange }) => ( <tr> <td> <input id={rowId + 'name'} type="text" value={name} onChange={(e) => onFieldChange({ id: rowId, field: 'name', value: e.target.value, }) } /> </td> <td> <input id={rowId + 'proportion'} type="text" value={proportion} onChange={(e) => onFieldChange({ id: rowId, field: 'proportion', value: e.target.value, }) } /> </td> <td> <button type="button">-</button> </td> </tr> ); const IngredientsTable = () => { const [rowsGenerated, setRowsGenerated] = useState(3); const [ingredients, setIngredients] = useState({ 1: { id: '1', name: '', proportion: '' }, 2: { id: '2', name: '', proportion: '' }, 3: { id: '3', name: '', proportion: '' }, }); const onFieldChange = ({ value, id, field }) => { setIngredients((prevState) => ({ ...prevState, [id]: { ...prevState[id], [field]: value }, })); }; const addRow = () => { // 使用函数式更新获取最新状态,解决闭包问题 setRowsGenerated((prevCount) => { const newId = prevCount + 1; setIngredients((prevState) => ({ ...prevState, [newId]: { id: newId.toString(), name: '', proportion: '' }, })); return newId; }); }; return ( <div className="ingredient_table"> <table> <thead> <tr> <th style={{ width: '62%' }}>Name </th> <th style={{ width: '30%' }}>Proportion %</th> <th style={{ width: '8%' }}></th> </tr> </thead> <tbody> {Object.keys(ingredients).map((id) => ( <Row key={`ingredient${id}`} rowId={id} name={ingredients[id].name} proportion={ingredients[id].proportion} onFieldChange={onFieldChange} /> ))} </tbody> </table> <button className="ingredient_add_row_button" onClick={addRow}> Add row </button> </div> ); }; export default IngredientsTable;
方案2:用React.memo包裹内部Row组件(备选)
如果不想迁移Row组件,可通过React.memo缓存组件,仅当props变化时才重渲染:
// 在IngredientsTable内部修改Row组件定义 const Row = React.memo(({ rowId, name, proportion, onFieldChange }) => ( <tr> {/* 输入框内容与之前一致 */} </tr> ));
同时需要按照方案1中的方式修复addRow的闭包问题。
额外说明
- 输入框上的
key属性无需单独设置,Row组件的key已经可以标识行的唯一性,React会通过该key追踪行的变化。 - 修复闭包问题后,新增行时不会出现ID重复或错误的情况。
内容的提问来源于stack exchange,提问作者Eric P
相关产品推荐
相关产品推荐

