React状态对象中修改数组元素时Input失焦问题求助
解决React输入框输入时失焦的问题
问题原因
输入框每次输入字符就失焦,核心原因是组件定义在父组件的渲染函数内部:EditFieldRow、EditField、PageContent这三个组件都定义在GetProgram组件内部。每当GetProgram的状态(比如exercises)更新时,父组件会重新渲染,这三个组件会被重新定义为全新的组件类型。React会卸载旧的组件实例,再挂载新的,导致输入框失去焦点。
另外,updateField函数直接引用旧的exercises状态快照,可能导致状态更新不及时,加剧组件重复渲染的问题。
修复步骤
1. 将内部组件移到父组件外部
把EditFieldRow、EditField、PageContent从GetProgram组件内部移出,确保它们不会在每次父组件渲染时被重新定义:
// 移到GetProgram组件外部 const EditFieldRow = ({ set, reps, weight, onUpdate }) => { return ( <tr> <td>{set}</td> <td><input type="text" value={reps} onChange={(e) => onUpdate({ set, reps: e.target.value })}/></td> <td><input type="text" value={weight} onChange={(e) => onUpdate({ set, weight: e.target.value })}/></td> </tr> ) } const EditField = ({ exercise, j, onUpdate }) => { return ( <div> <form onSubmit={(e) => {e.preventDefault(); console.log("submitted")}}> <table className="table table-bordered table-colored"> <thead> <tr> <th>Set</th> <th>Reps</th> <th>Weight</th> </tr> </thead> <tbody> {[...Array(exercise.sets).keys()].map((i) => { return ( <EditFieldRow key={`fieldrow-${j}-${i}`} set={i+1} reps={exercise.reps[i]} weight={exercise.weight[i]} onUpdate={(data) => onUpdate({...data, j})} /> ) })} </tbody> </table> </form> </div> ) } const PageContent = ({ program, exercises, editingExerciseIndex, onEditExercise, onUpdateField }) => { return ( <div className="container-fluid page-content program-page" > <h2 style={{textAlign:'center'}}>{program.rname??program.name} Program</h2> <div className="row"> {program.dayMap.map((day, i) => { return ( <div className="col" key={`${day}-${i}`}> <h4>{day}</h4> <hr /> <table className="lift-table table table-bordered table-colored"> <thead> <tr> <th>Name</th> <th>Sets</th> </tr> </thead> <tbody> {exercises.map((exercise, j) => { if (exercise.day === i+1) { return ( <React.Fragment key={`exercise-${exercise.position}-${j}`}> <tr id={`exercise-row-${exercise.name.replaceAll(" ", "-")}`} className={`exercise-row`} onClick={() => onEditExercise(j)} > <td>{exercise.name}</td> <td>{exercise.sets}</td> </tr> {editingExerciseIndex === j && ( <tr> <td colSpan="2"> <EditField exercise={exercise} j={j} onUpdate={onUpdateField} /> </td> </tr> )} </React.Fragment> ) } return null; })} </tbody> </table> </div> ) })} </div> </div> ) } // 原GetProgram组件 export default function GetProgram() { const [exercises, setExercises] = useState([]) const [editingExerciseIndex, setEditingExerciseIndex] = useState(-1) const [loading, setLoading] = useState([true, true]) const [program, setProgram] = useState({ days: [], name : '', _id : null }) useEffect(() => { async function getProgramInfo() { fetch(`http://localhost:5000/program`) .then(res => res.json()) .then(body => { setProgram(body) setLoading([false, loading[1]]) }) .catch(err => console.log(`**ERR: ${err}`)) } getProgramInfo() }, []) useEffect(() => { async function getExercises() { fetch(`http://localhost:5000/program/getmap`) .then(res => res.json()) .then(body => { setExercises(body) setLoading([loading[0], false]) }) } getExercises() }, []) // 优化updateField,使用函数式更新获取最新状态 const updateField = useCallback((props) => { setExercises(prevExercises => { const exerciseToUpdate = prevExercises[props.j]; if (!exerciseToUpdate) return prevExercises; let newExercise; if (props.reps !== undefined) { const newReps = [...exerciseToUpdate.reps]; newReps[props.set - 1] = parseInt(props.reps, 10) || 0; newExercise = {...exerciseToUpdate, reps: newReps}; } else { const newWeight = [...exerciseToUpdate.weight]; newWeight[props.set - 1] = parseInt(props.weight, 10) || 0; newExercise = {...exerciseToUpdate, weight: newWeight}; } return prevExercises.map(exercise => exercise.day === newExercise.day && exercise.position === newExercise.position ? newExercise : exercise ); }); }, []); if (program.dayMap) { return ( <PageContent program={program} exercises={exercises} editingExerciseIndex={editingExerciseIndex} onEditExercise={setEditingExerciseIndex} onUpdateField={updateField} /> ) } return <div></div> }
2. 使用函数式状态更新
修改updateField函数,使用setExercises的函数式更新形式,确保每次基于最新状态修改,避免闭包捕获旧状态的问题。同时用useCallback包裹该函数,避免传递给子组件时触发不必要的重渲染。
3. 优化Key值
确保所有列表项的key稳定且唯一,比如使用exercise.position+索引,减少React的不必要重渲染。
额外优化点
- 给
form添加e.preventDefault(),避免提交时页面刷新 - 将
exercises.map放在单独的<tbody>里,避免生成多个<tbody>(不符合HTML规范) - 移除输入框的冗余
key,父组件的key已足够区分不同行
内容的提问来源于stack exchange,提问作者chronicvenom
相关产品推荐
相关产品推荐

