You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多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时:

  1. 触发getStudents()后,两个reducer都会进入case 'FETCH_ALL'分支,students和teachers的state都会被替换成学生列表数据
  2. 触发getTeachers()后,两个reducer再次响应,students和teachers的state又会被替换成教师列表数据
  3. 最终导致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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 03:35:46