React reducer更新文本框数组时焦点丢失的解决方案
问题
在React组件中维护了一个包含对象数组的state,通过循环该数组渲染多个文本框。但在文本框中输入单个字符后,焦点会被移除,需要重新点击才能继续输入。推测问题出在reducer中创建新数组返回导致重渲染,但不想使用Redux,且reducer内还有其他字段,请问如何更新状态同时保持文本框焦点?
原代码示例:
const reducer = (state, action) => { switch (action.type) { case 'taskUpdate': { const index = state.tasks.findIndex(task => task.id == action.payload.id); const newArray = [...state.tasks]; newArray[index].task = action.payload.task; newArray[index].startDate = action.payload.startDate; newArray[index].endDate = action.payload.endDate; return { ...state, tasks: newArray, } }; default: throw new Error(); } }; const TenderCreate = () => { const [state, dispatch] = useReducer(reducer, { tasks: [{ id: 1, task: '', errorTask:'', startDate: '', errorStartDateTime:'', endDate:'', errorEndDateTime: '' }, ] }); const TaskRow = ({ data }) => ( <Form.Group as={Row} controlId="task1" className="mb-3" > <Form.Control type="text" placeholder="" name="task" className={data.errorTask ? 'is-invalid' : ''} value={data.task} onChange={e => { dispatch({ type: 'taskUpdate', payload: { id: data.id, task: e.target.value, startDate: data.startDate, endDate: data.endDate } }); }} /> </Form.Group> ); return ( <div> {state.tasks.map(task => ( <TaskRow data={task} key={task.id} /> ))} </div> ); }
解决方法
焦点丢失的核心原因是**TaskRow组件被定义在TenderCreate组件内部**,每次TenderCreate重渲染时,TaskRow都会被重新创建为全新的组件类型,React会卸载旧组件并挂载新组件,直接导致焦点丢失。这和reducer更新数组的方式无关。
具体解决步骤:
- 将
TaskRow组件移到TenderCreate组件外部,避免父组件重渲染时反复创建新组件 - 保持reducer中正确的不可变更新逻辑(你的原reducer逻辑没问题,可额外优化对象更新的不可变性)
修改后的完整代码:
import { useReducer } from 'react'; import { Form, Row } from 'react-bootstrap'; // 把TaskRow移到父组件外部,避免每次重渲染被重新创建 const TaskRow = ({ data, dispatch }) => ( <Form.Group as={Row} controlId={`task-${data.id}`} className="mb-3"> <Form.Control type="text" placeholder="" name="task" className={data.errorTask ? 'is-invalid' : ''} value={data.task} onChange={e => { dispatch({ type: 'taskUpdate', payload: { id: data.id, task: e.target.value, startDate: data.startDate, endDate: data.endDate } }); }} /> </Form.Group> ); const reducer = (state, action) => { switch (action.type) { case 'taskUpdate': { const index = state.tasks.findIndex(task => task.id === action.payload.id); const newArray = [...state.tasks]; // 优化:采用不可变方式更新对象,符合React状态更新规范 newArray[index] = { ...newArray[index], task: action.payload.task, startDate: action.payload.startDate, endDate: action.payload.endDate }; return { ...state, tasks: newArray, }; } default: throw new Error(); } }; const TenderCreate = () => { const [state, dispatch] = useReducer(reducer, { tasks: [{ id: 1, task: '', errorTask:'', startDate: '', errorStartDateTime:'', endDate:'', errorEndDateTime: '' }] }); return ( <div> {state.tasks.map(task => ( <TaskRow key={task.id} data={task} dispatch={dispatch} /> ))} </div> ); } export default TenderCreate;
额外优化说明:
- 给
Form.Group的controlId设置动态值(task-${data.id}),避免多个行使用相同ID,符合表单组件规范 - 用
...newArray[index]的方式更新对象,保持状态不可变性,避免潜在的状态更新问题
内容的提问来源于stack exchange,提问作者Kasun
相关产品推荐
相关产品推荐

