多dispatch场景下useSelector获取状态异常问题排查
问题:Redux状态覆盖导致组件展示数据错误
问题场景
父组件在useEffect中依次dispatch三个异步action后,出现状态混乱:
<StudentInfo/>组件显示的是teacher状态的长度<TeacherInfo/>组件显示的是student状态的长度
父组件代码:
const dispatch = useDispatch() useEffect(() => { dispatch(getStudents()); dispatch(getTeachers()); dispatch(getSections()); }, [dispatch])
<StudentInfo/>组件代码:
export default function StudentInfo() { const students = useSelector((state) => state.students); // 不必要的ref使用 const studentsRef = React.useRef(students); useEffect(() => { studentsRef.current = students; }, [students]) return( <> <Box style={{ paddingTop: '1.5rem', height: '2rem'}}> <SchoolSharpIcon sx={{fontSize: '40px'}} /> </Box> <Box style={{fontSize: '1rem'}}> Number of Students enrolled: {studentsRef.current.length} </Box> </> ) }
<TeacherInfo/>组件采用类似逻辑获取teachers状态长度,而students和teachers的reducer均使用'FETCH_ALL'作为action type:
students reducer:
export function students (students = [], action) { switch (action.type) { case 'FETCH_ALL': console.log(action.payload) console.log("students?") return action.payload; case 'CREATE': return [...students, action.payload]; default: return students; } }
teachers reducer:
export function teachers (teachers = [], action) { switch (action.type) { case 'FETCH_ALL': console.log(action.payload) console.log("teachers?") return action.payload; case 'CREATE': return [...teachers, action.payload]; default: return teachers; } }
核心原因
Redux中所有reducer会响应每一个action。当getStudents()和getTeachers()的action共用'FETCH_ALL'这个type时:
- 触发
getStudents()后,两个reducer都会进入case 'FETCH_ALL'分支,students和teachers的state都会被替换成学生列表数据 - 触发
getTeachers()后,两个reducer再次响应,students和teachers的state又会被替换成教师列表数据 - 最终导致students和teachers的state数据混乱,组件展示错误的长度
另外,<StudentInfo/>中使用useRef是不必要的,useSelector已经会订阅state变化,组件会自动重新渲染,直接使用students.length即可。
解决方案
1. 为不同模块的action type添加唯一前缀
修改reducer中的action type,确保每个模块的type唯一:
students reducer:
export function students (students = [], action) { switch (action.type) { // 改为唯一的模块前缀type case 'STUDENTS/FETCH_ALL': return action.payload; case 'STUDENTS/CREATE': return [...students, action.payload]; default: return students; } }
teachers reducer:
export function teachers (teachers = [], action) { switch (action.type) { // 改为唯一的模块前缀type case 'TEACHERS/FETCH_ALL': return action.payload; case 'TEACHERS/CREATE': return [...teachers, action.payload]; default: return teachers; } }
同时修改对应的action creator,确保dispatch的action type与reducer匹配:
// students的action creator export const getStudents = () => { return async (dispatch) => { // 异步请求逻辑... dispatch({ type: 'STUDENTS/FETCH_ALL', payload: 学生数据 }); } } // teachers的action creator export const getTeachers = () => { return async (dispatch) => { // 异步请求逻辑... dispatch({ type: 'TEACHERS/FETCH_ALL', payload: 教师数据 }); } }
2. 移除组件中不必要的useRef
修改<StudentInfo/>组件,直接使用useSelector返回的state值:
export default function StudentInfo() { const students = useSelector((state) => state.students); return( <> <Box style={{ paddingTop: '1.5rem', height: '2rem'}}> <SchoolSharpIcon sx={{fontSize: '40px'}} /> </Box> <Box style={{fontSize: '1rem'}}> Number of Students enrolled: {students.length} </Box> </> ) }
<TeacherInfo/>组件做同样修改,直接使用teachers.length即可。
内容的提问来源于stack exchange,提问作者Charlz Ivan John Babida
相关产品推荐
相关产品推荐

