我真的请求你的帮助。问题是,我不明白如何在卸载组件之前清理 redux 状态。我有选择链表,当您选择其中任何一个时,会发出一个 api 请求,并且其他的数据会发生变化。一切正常,直到我从一个这样的页面转到另一个页面。那些。我有标签,在一个标签上有链接列表,在另一个标签上。如果我切换选项卡,那么我的请求会加倍,因为 useEffect 中的清理功能没有时间清除状态,因此,当加载组件时,会发生第一个请求链,当观察到 select 时,第二个立即发生。如何在加载另一个选项卡之前等待清理功能完成?
useEffect(() => {
dispatch(fetchCoursesByGroup({ idPlan, idGroup })); // Получаю данные для заполнения первого списка
// В reducer просто обновляю state пустым массивом
return () => {
dispatch(changePlan(planFields)); // Очищаю таблицу, которая была получена на основе выбранных значений из списка
dispatch(setAllFormControl([])); // Очищаю Формы контроля
dispatch(setSemestersCourse([])); // Очищаю курсы
dispatch(setGroupCourses([])); // Очищаю семестры
};
}, []);
我通过相同的 useEffect 获取所有列表,例如:
useEffect(() => {
if (watchCourse) getSemesters(watchCourse); // Если выбран курс, получаю семестры на этот курс
}, [watchCourse]);
最后,执行一个函数,其中也用select中选择的值向服务器发起api请求,获取表。混乱来了。由于 useEffect 没有时间清理课程、学期、控制表格等,数据还在,当页面加载时,立即触发获取表的请求,然后触发下一个 useEffect,其中,当页面被加载,接收第一个列表的数据,这导致链 useEffect 获取后续列表的数据,最后重新查询表......
带有链表的完整组件代码
import React, { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { useDispatch, useSelector } from 'react-redux';
import { fetchCoursesByGroup } from '../../../store/Actions/coursesAction';
import { fetchSemestersByCourse } from '../../../store/Actions/semestersAction';
import { fetchAllFormControl } from '../../../store/Actions/formControlAction';
import { fetchPlan } from '../../../store/Actions/planAction';
import { planReducer } from '../../../store/Reducers/planReducer';
import { formControlReducer } from '../../../store/Reducers/formControlReducer';
import { semestersReducer } from '../../../store/Reducers/semestersReducer';
import { coursesReducer } from '../../../store/Reducers/coursesReducer';
import { plan as planFields } from '../../../store/fields/planFields';
import Filter from '../../Basic/Filter';
function PlanFilter({ idGroup, idPlan, callbackSemester = () => {}, callbackControl = () => {} }) {
const dispatch = useDispatch();
const { register, watch, setValue } = useForm();
const watchCourse = watch('coursesSelect');
const watchSemester = watch('semestersSelect');
const watchControl = watch('controlsSelect');
const coursesSelect = register('coursesSelect');
const semestersSelect = register('semestersSelect');
const controlsSelect = register('controlsSelect');
const courses = useSelector((state) => state.coursesReducer.groupCourses);
const semesters = useSelector((state) => state.semestersReducer.semestersCourse);
const formControl = useSelector((state) => state.formControlReducer.all);
const { changePlan } = planReducer.actions;
const { setAllFormControl } = formControlReducer.actions;
const { setSemestersCourse } = semestersReducer.actions;
const { setGroupCourses } = coursesReducer.actions;
// Получаю курсы в первый select и очищаю при размонтировании компонента
useEffect(() => {
dispatch(fetchCoursesByGroup({ idPlan, idGroup }));
return () => {
dispatch(changePlan(planFields));
dispatch(setAllFormControl([]));
dispatch(setSemestersCourse([]));
dispatch(setGroupCourses([]));
};
}, []);
// Здесь я хочу чтоб при первой загрузке компонента установился первый элемент из массива как выбранный курс, чтоб воспроизвести цепочку и заполнить все списки сразу при загрузке страницы
useEffect(() => {
if (courses.length > 0 && !watchCourse)
setValue('coursesSelect', courses[0].idCoursePlan.toString());
}, [courses]);
useEffect(() => {
if (semesters.length > 0) setValue('semestersSelect', semesters[0].name.split(' ')[1]);
}, [semesters]);
useEffect(() => {
if (formControl.length > 0) {
setValue('controlsSelect', formControl[0].id.toString());
getPlan(); // Вызов финальной таблицы
}
}, [formControl]);
// При изменении семестра вызываю функцию для получения через api форм контроля на выбранный семестр
useEffect(() => {
if (watchSemester) {
getFormControl();
callbackSemester(watchSemester); // Просто возвращаю выбранный семестр в родителя для других целей
}
}, [watchSemester]);
useEffect(() => {
if (watchCourse) getSemesters(watchCourse);
}, [watchCourse]);
useEffect(() => {
if (watchControl) {
getPlan();
callbackControl(watchControl);
}
}, [watchControl]);
const getSemesters = (idCoursePlan) =>
dispatch(fetchSemestersByCourse({ idCoursePlan, idGroup, withActive: true }));
const getFormControl = () => dispatch(fetchAllFormControl({ withAll: false }));
const getPlan = () => {
if (watchSemester && watchControl && idPlan)
dispatch(fetchPlan({ semester: watchSemester, idControl: watchControl, idPlan }));
};
return (
<Filter.Form classes={['FilterForm FilterForm_position_sticky pb-4']}>
<Filter.Group labelTitle="Курс:">
<Filter.Select registered={coursesSelect}>
{courses.map((course) => (
<Filter.Option key={course.idCoursePlan} value={course.idCoursePlan}>
{course.name}
</Filter.Option>
))}
</Filter.Select>
</Filter.Group>
<Filter.Group labelTitle="Семестр:" classes={['ml-4']}>
<Filter.Select registered={semestersSelect}>
{semesters.map((semester) => (
<Filter.Option key={semester.id} value={semester.name.split(' ')[1]}>
{semester.name}
</Filter.Option>
))}
</Filter.Select>
</Filter.Group>
<Filter.Group labelTitle="Форма контроля:" classes={['ml-4']}>
<Filter.Select registered={controlsSelect}>
{formControl.map((control) => (
<Filter.Option key={control.id} value={control.id}>
{control.name}
</Filter.Option>
))}
</Filter.Select>
</Filter.Group>
</Filter.Form>
);
}
export default PlanFilter;
减速器之一的代码(课程)
import { createSlice } from '@reduxjs/toolkit';
import { fetchCoursesByFacultyLevelForm, fetchCoursesByGroup } from '../Actions/coursesAction';
const initialState = {
groupCourses: [], // вот этот массив используется для select'a
facultyLevelForm: [],
isLoading: false,
error: '',
};
const pendingReducer = (state) => {
state.isLoading = true;
};
const rejectedReducer = (state, action) => {
state.isLoading = false;
if (action) state.error = action.payload.error;
};
const defaultFulfilledReducer = (state) => {
rejectedReducer(state);
};
export const coursesReducer = createSlice({
name: 'courses',
initialState,
reducers: {
setGroupCourses(state, action) {
// Сюда я передаю пустой массив при
// размонтировании компонента, это я и называю очищением
state.groupCourses = action.payload;
},
changeFacultyLevelForm(state, action) {
state.facultyLevelForm = action.payload;
},
},
extraReducers: {
[fetchCoursesByGroup.fulfilled]: (state, action) => {
state.groupCourses = action.payload;
defaultFulfilledReducer(state); // Заполняю курсы с api
},
[fetchCoursesByGroup.pending]: pendingReducer,
[fetchCoursesByGroup.rejected]: rejectedReducer,
[fetchCoursesByFacultyLevelForm.fulfilled]: (state, action) => {
state.facultyLevelForm = action.payload;
defaultFulfilledReducer(state);
},
[fetchCoursesByFacultyLevelForm.pending]: pendingReducer,
[fetchCoursesByFacultyLevelForm.rejected]: rejectedReducer,
},
});
export default coursesReducer.reducer;
所有其他减速器的制造方式完全相同。
安装所有选项卡的主页代码。
<BaseLayout
callbackSelectedGroup={(e) => setSelectedGroup(e)}
tabs={selectedGroup ? ['Контингент', 'Учебный план', 'Успеваемость'] : []}
callbackSelectedTab={(e) => setTab(e)}
>
{tab === 1 && <Contingent idGroup={group.id} />}
{tab === 2 && <Plan idGroup={group.id} />}
{tab === 3 && <Progress idGroup={group.id} />}
</BaseLayout>
代码选项卡计划(课程)
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import withReactContent from 'sweetalert2-react-content';
import Swal from 'sweetalert2';
import { PencilAltIcon } from '@heroicons/react/outline';
import FileDropper from '../../FileDropper';
import {
fetchPlan,
fetchPlanIdByGroup,
savePlan as savePlanAction,
uploadPlan as uploadFileDispatch,
} from '../../../store/Actions/planAction';
import HeaderPlan from './HeaderPlan';
import BodyPlan from './BodyPlan';
import PlanFilter from './PlanFilter';
import { ContextItem, ContextList, ContextMenu } from '../../ContextMenu';
import { ReactComponent as NotFoundSvg } from '../../../images/undraw_not_found.svg';
import MTable from '../../Tables/MainTable';
function Plan() {
const TheSwal = withReactContent(Swal);
const dispatch = useDispatch();
// Store
const idPlan = useSelector((store) => store.planReducer.idPlan);
const plan = useSelector((store) => store.planReducer.plan);
const group = useSelector((store) => store.groupsReducer.selectedGroup);
// State
const [editMode, setEditMode] = useState(false);
const [selectedSemester, setSelectedSemester] = useState();
const [selectedControl, setSelectedControl] = useState();
useEffect(() => {
if (group)
dispatch(
fetchPlanIdByGroup({
idSpecialty: group.spesialty_id,
yearAdmission: group.group_date_start,
}),
);
}, [group]);
return (
<div className="Main__ContentContainer">
{idPlan.id && (
{/* Это компонент со связными списками, код которого я предоставил выше*/}
<PlanFilter
idPlan={idPlan.id}
idGroup={group.id}
callbackSemester={(e) => setSelectedSemester(e)}
callbackControl={(e) => setSelectedControl(e)}
/>
)}
{idPlan.id && (
<>
<MTable.Table>
<HeaderPlan
editMode={editMode}
callbackCancelEdit={cancelEdit}
callbackSaveEdit={savePlan}
/>
<BodyPlan
editMode={editMode}
callbackCancelEdit={cancelEdit}
callbackSaveEdit={savePlan}
/>
</MTable.Table>
</>
)}
export default Plan;
再现情况:
- 第一个选项卡已加载(特遣队见截图 1 或 2)
- 我去那里的第二个(课程)执行所需数量的请求(4个请求:获取课程、学期、控制表格和决赛桌)
- 我转向第三个选项卡(进度),其中的结构与第二个选项卡上的结构完全相同,相同的链表。这里已经发生了一场狂欢,请求被重复,useEffect 链执行了两次,因为自上一个选项卡以来数据还没有被清理过。
- 我返回到第二个选项卡,请求再次重复,因为现在第三个选项卡没有时间自行清理。如果我在第一个和第二个选项卡之间切换,一切都会好起来的,因为第一个没有链接列表,它不会引起对课程的请求等等,所以这两个选项卡之间一切都会好起来的,但是在选项卡之间使用相同的相同请求填充链表,来自存储的相同状态,这样的事件就会发生。
总的来说,我想出了一个解决方案,我不知道它是否正确,但在这种情况下我只是拒绝使用 store。结果,现在一切看起来像这样:
不知道这是多么正确,但它确实有效。