Redux Store对象数组更新引发输入框失焦问题求助
输入框失焦问题:React+Redux管理数组状态时的重渲染解决方案
问题描述
在React项目中使用Redux管理董事信息的对象数组状态,修改输入框内容时Redux状态能成功更新,但组件会触发重渲染,导致输入框失去焦点,无法流畅输入。尝试过直接修改状态、使用filter替代map的方式,均未解决问题。
组件代码
import { Box, Button, Stack, TextField, Typography } from "@mui/material"; import React from "react"; import { useDispatch, useSelector } from "react-redux"; import { v4 as uuidv4 } from "uuid"; import { handleDirectorAdd, handleDirectorInfoFormChange, handleDirectorRemove, } from "../../../../redux/slices/directorInfo"; const DirectorsInformation = () => { const dispatch = useDispatch(); const directorsInfo = useSelector((state) => state.directorInfo); const handleChange = (e, index) => { const { name, value } = e.target; if (name && value) { dispatch(handleDirectorInfoFormChange({ name, value, index })); } }; const handleAdd = () => { dispatch(handleDirectorAdd()); }; const handleRemove = (e, index) => { dispatch(handleDirectorRemove({ index })); }; return ( <form onSubmit={handleSubmit} noValidate style={{ padding: "1rem", backgroundColor: "white", border: "1px solid darkgrey", textAlign: "center", }} > <Typography variant="body1"> <strong>Director's Information</strong> </Typography> <hr /> {directorsInfo?.map((directorInfo, index) => ( <div key={uuidv4()}> <Stack spacing={1} alignItems="center" style={{ padding: "1rem", backgroundColor: "rgb(227, 227, 227)", }} > <Typography variant="body1">Director's Information</Typography> <Button variant="contained" color="error" sx={{ textTransform: "none" }} onClick={(e) => handleRemove(e, index)} > Remove </Button> <Typography variant="body1"> <strong>Full Name</strong> </Typography> <TextField fullWidth size="small" label="Full Name" name="name" value={directorInfo.name} onChange={(e) => handleChange(e, index)} sx={{ backgroundColor: "white" }} /> <Typography variant="body1"> <strong>Official Email</strong> </Typography> <TextField fullWidth size="small" label="Official Email" name="email" type="email" value={directorInfo.email} onChange={(e) => handleChange(e, index)} sx={{ backgroundColor: "white" }} /> <Typography variant="body1"> <strong>Contact Number</strong> </Typography> <TextField fullWidth size="small" label="Contact Number" name="contactNumber" value={directorInfo.contactNumber} onChange={(e) => handleChange(e, index)} sx={{ backgroundColor: "white" }} /> <Typography variant="body1"> <strong>Title</strong> </Typography> <TextField fullWidth size="small" label="Tile" name="title" type="string" value={directorInfo.title} onChange={(e) => handleChange(e, index)} sx={{ backgroundColor: "white" }} /> </Stack> <Button variant="contained" color="success" sx={{ textTransform: "none", margin: "2rem 0" }} onClick={handleAdd} > Add Directors Information </Button> </div> ))} </form> ); }; export default DirectorsInformation;
Reducer及Action代码
import { createSlice, current } from "@reduxjs/toolkit"; const initialValues = { name: "", email: "", contactNumber: "", username: "", title: "", }; const initialState = [initialValues]; const directorInfo = createSlice({ name: "directorInfo", initialState, reducers: { handleDirectorAdd(state, action) { return [...state, initialValues]; }, handleDirectorRemove(state, action) { return state.filter((_, index) => index !== action.payload.index); }, handleDirectorInfoFormChange(state, action) { const { index, name, value } = action.payload; return state.map((director, idx) => { if (idx === index) { return { ...director, [name]: value, }; } else { return director; } }); }, }, }); export const { handleDirectorAdd, handleDirectorRemove, handleDirectorInfoFormChange, } = directorInfo.actions; export default directorInfo.reducer;
解决方案
1. 修复列表项的key值(核心问题)
输入框失焦的根本原因是列表渲染时使用了动态生成的uuidv4()作为key。每次组件重渲染,uuidv4()都会生成新的字符串,React会将其识别为全新的组件,卸载旧的输入框并重新挂载新的,导致焦点丢失。
修改步骤:
- 更新initialValues,添加唯一id字段:
import { v4 as uuidv4 } from "uuid"; const initialValues = { id: uuidv4(), // 添加唯一id name: "", email: "", contactNumber: "", username: "", title: "", }; - 修改handleDirectorAdd动作,每次添加新董事时生成新id:
handleDirectorAdd(state, action) { return [...state, {...initialValues, id: uuidv4()}]; // 生成新的唯一id }, - 组件中使用director的id作为key:
{directorsInfo?.map((directorInfo, index) => ( <div key={directorInfo.id}> {/* 替换uuidv4()为directorInfo.id */} // ... 其余内容不变 </div> ))}
2. 优化useSelector的重渲染触发逻辑
为了避免不必要的组件重渲染,可以使用shallowEqual来对比状态数组,只有当数组或内部元素的引用发生变化时才更新组件:
import { useDispatch, useSelector, shallowEqual } from "react-redux"; // ... const directorsInfo = useSelector((state) => state.directorInfo, shallowEqual);
3. 修复小错误
组件中TextField的label写成了"Tile",应该修正为"Title":
<TextField fullWidth size="small" label="Title" // 修正拼写错误 name="title" type="string" value={directorInfo.title} onChange={(e) => handleChange(e, index)} sx={{ backgroundColor: "white" }} />
说明
通过给每个董事对象分配固定的唯一id作为列表项key,React能够正确识别列表项的身份,重渲染时只会更新对应项的内容,而不会卸载并重新挂载组件,输入框的焦点就能保持稳定。
内容的提问来源于stack exchange,提问作者Sajeel Zafar
相关产品推荐
相关产品推荐

