redux导致40+单页上的重新租户:想要帮助识别/修复问题
我正在为音乐理论应用程序公司的仪表板网站工作,用户可以在该网站上管理其信息,课程,作业,媒体内容等。
该网站是使用React Hooks和Redux Toolkit构建的。我正在处理的当前页面是教师用户编辑作业集(分配详细信息和与该分配相关的任何练习)。我们希望用户能够通过键入来导航到每个分配编辑页面,因此URL类似于course/{CourseID}/sigsmenment/aissignment/{sigsionmentId}
。这意味着任何时候加载此页面,我需要进行API调用以获取我需要的任何后端数据。
我的整体Redux商店对象非常简单。这是与我在有关页面上抓取的内容有关的示例:
store: {
singleCourse: {
courseInfo: <courseInfoObj>,
students: [...<studentsObjs>],
assignments: [...<assignmentObjs>],
},
singleAssignment: {
assignmentInfo: <assignmentInfoObj>,
exercises: [...<exerciseObjs>],
},
contentLibrary: {
library: [...<libraryObjs>]
}
}
页面是构造的,因此我有一个useffect
使用batch
将所有派遣我需要采取的操作。然后,我用
use -elector
从Redux Store中获取所需的东西。在此页面上,我需要dispatch
6个不同的操作(到6个不同的API路由)才能获取我需要的所有数据,该数据存储在上面列出的3个还原器上。要从Redux获取数据,我正在执行const {singleCourse,singleSignment,contentLibrary} = useselector(state =&gt; state)
我注意到该页面的重新呈现高达40+时间(在作业中列出的练习越多,重新汇款越多)。在我的调试中,我注意到,当我删除任何use-elector
呼叫时,重新租赁的数量会大大下降。请注意,我正在通过在我的功能页面之前定义的数字变量来跟踪渲染器,并在我的JSX返回之前将其递增/记录。
使用Esselector从Redux商店中抓住东西可以成为这种疯狂的重新租赁数量的原因吗?我已经做了一些有关使用记忆的选择器来防止重新渲染过多的研究(并且我可以使用Redux Toolkit中使用CreateSelector
),但是这些示例与我的案例完全无关。我的生活我不知道如何在我的情况下使用它。
另外,在此特定页面上,我派遣6个不同的动作来更新3个单独的还原器是不好的做法吗?
这些页面的加载很快并且反应迅速,但是我担心所有这些重新汇款都会/会引起重大问题。
谢谢
编辑:这是更多代码供参考。
sizhtmentDetailpage.tsx
const AssignmentDetailPage = () => {
const { courseID, assignmentID } = useParams();
// dispatches
useEffect(() => {
batch(() => {
dispatch(getCourseDetail(courseID));
dispatch(getCourseStudents(courseID));
dispatch(getCourseAssignments(courseID));
dispatch(getAssignmentAndDocuments(assignmentID));
dispatch(listAssignmentDueDateExtensions(assignmentID));
dispatch(getEntireContentLibrary());
})
}, [dispatch]);
// Grabbing from Redux store.
const selectedCourse = useHarmoniaSelector(state=> state.selectedCourse);
const selectedAssignment = useHarmoniaSelector(state => state.selectedAssignment);
const contentLibrary = useHarmoniaSelector(state => state.contentLibrary);
// Grabbing assignment info from assignments state.
const assignment = selectedAssignment.assignment as Assignment || {};
const dueDateExtensions = selectedAssignment.dueDateExtensions as DueDateExtension[];
const documents = selectedAssignment.documents || [] as Document[];
// Grabbing array of assignments and finding assignmentIndex of current assignment.
const courseAssignments = selectedCourse.assignments as Assignment[] || [];
const courseStudents = selectedCourse.students || [] as CourseStudent[];
}
这是一个带有selectedAssignment的redux东西的示例:
selectedAssignment.ts
type SelectedAssignmentInitialState = {
assignment?: Assignment | null,
documents?: Document[] | null,
dueDateExtensions?: DueDateExtension[] | null
}
const initialState: SelectedAssignmentInitialState = {};
// actions
export const getAssignmentAndDocuments = createAsyncThunk
<Assignment, number, {rejectValue: void}>
('assignments/getAssignmentDetail', async(assignmentID, {dispatch, rejectWithValue}) => {
try{
const response = await API.get(`/api/assignments/${assignmentID}`);
return response.data.data as Assignment;
}catch(e){
dispatch(pushErrorNotification(errorMessage(e.data)));
return rejectWithValue();
}
});
export const listAssignmentDueDateExtensions = createAsyncThunk<DueDateExtension[], number, {rejectValue: void} >('assignment/listAssignmentDueDateExtensions', async (assignmentID, {dispatch, rejectWithValue}) => {
try{
const response = await API.get(`/api/assignments/${assignmentID}/due-day-extensions`);
return response.data.data as DueDateExtension[];
}catch(e){
dispatch(pushErrorNotification(e.data));
return rejectWithValue();
}
});
// reducer
const selectedAssignmentSlice = createSlice({
name: 'selectedAssignment',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(
getAssignmentAndDocuments.fulfilled,
((state, {payload}) => {
// assignmentDetail is everything but documents; documents kept on separate key in state obj to keep things simpler.
const {course, documents, due_at, id, released_at, set_key, show_after_due, title, weight} = payload;
state.assignment = {course, due_at, id, released_at, set_key, show_after_due, title, weight};
state.documents = documents;
})
)
.addCase(listAssignmentDueDateExtensions.fulfilled, ((state, {payload}) => {
state.dueDateExtensions = payload;
}))
I am working on the dashboard site for a music theory app company where users can manage their info, courses, assignments, media content etc.
The site is built using React hooks and Redux Toolkit. The current page I am working on is for a teacher user to edit an assignment set (assignment details and any exercises associated with that assignment). We want the user to be able to navigate to each assignment edit page by typing, so the url is something like course/{courseID}/assignment/{assignmentID}
. This means that anytime this page loads I need to make API calls to grab any back-end data I need.
My overall Redux store object is fairly simple. Here's an example of it pertaining to what I'm grabbing on the page in question:
store: {
singleCourse: {
courseInfo: <courseInfoObj>,
students: [...<studentsObjs>],
assignments: [...<assignmentObjs>],
},
singleAssignment: {
assignmentInfo: <assignmentInfoObj>,
exercises: [...<exerciseObjs>],
},
contentLibrary: {
library: [...<libraryObjs>]
}
}
The page is structured so I have a single useEffect
that uses batch
to group all dispatch
actions I need to make. Then, I grab what I need from the Redux store with useSelector
. On this page I need to dispatch
6 different actions (to 6 different API routes) to get all the data I need, which is stored on the 3 reducers listed above. To get the data from Redux, I'm doing const {singleCourse, singleAssignment, contentLibrary} = useSelector(state => state)
I've noticed that the page is re-rendering up to 40+ times (and the more exercises there are to list in an assignment, the more re-renders there are). In my debugging I've noticed that when I remove any useSelector
calls, the number of re-renders drops significantly. Note that I'm keeping track of the renders very crudely via a numeric variable defined just before my function page and incrementing/logging it before my JSX return.
Can grabbing things from the Redux store with useSelector be the cause of this insane amount of re-renders? I've done some research about using memoized selectors to prevent excessive re-rendering (and I can use createSelector
from Redux Toolkit for this), but the examples are not relevant at all to my case and for the life of me I can't figure out how to use it in my case.
Also, is it bad practice that on this particular page I dispatch 6 different actions that update 3 separate reducers?
The pages load fairly quickly and are responsive, but I'm worried that all these re-renders are/will cause major issues.
Thanks
Edit: here's more code for reference.
AssignmentDetailPage.tsx
const AssignmentDetailPage = () => {
const { courseID, assignmentID } = useParams();
// dispatches
useEffect(() => {
batch(() => {
dispatch(getCourseDetail(courseID));
dispatch(getCourseStudents(courseID));
dispatch(getCourseAssignments(courseID));
dispatch(getAssignmentAndDocuments(assignmentID));
dispatch(listAssignmentDueDateExtensions(assignmentID));
dispatch(getEntireContentLibrary());
})
}, [dispatch]);
// Grabbing from Redux store.
const selectedCourse = useHarmoniaSelector(state=> state.selectedCourse);
const selectedAssignment = useHarmoniaSelector(state => state.selectedAssignment);
const contentLibrary = useHarmoniaSelector(state => state.contentLibrary);
// Grabbing assignment info from assignments state.
const assignment = selectedAssignment.assignment as Assignment || {};
const dueDateExtensions = selectedAssignment.dueDateExtensions as DueDateExtension[];
const documents = selectedAssignment.documents || [] as Document[];
// Grabbing array of assignments and finding assignmentIndex of current assignment.
const courseAssignments = selectedCourse.assignments as Assignment[] || [];
const courseStudents = selectedCourse.students || [] as CourseStudent[];
}
Here's an example of redux stuff with the selectedAssignment:
selectedAssignment.ts
type SelectedAssignmentInitialState = {
assignment?: Assignment | null,
documents?: Document[] | null,
dueDateExtensions?: DueDateExtension[] | null
}
const initialState: SelectedAssignmentInitialState = {};
// actions
export const getAssignmentAndDocuments = createAsyncThunk
<Assignment, number, {rejectValue: void}>
('assignments/getAssignmentDetail', async(assignmentID, {dispatch, rejectWithValue}) => {
try{
const response = await API.get(`/api/assignments/${assignmentID}`);
return response.data.data as Assignment;
}catch(e){
dispatch(pushErrorNotification(errorMessage(e.data)));
return rejectWithValue();
}
});
export const listAssignmentDueDateExtensions = createAsyncThunk<DueDateExtension[], number, {rejectValue: void} >('assignment/listAssignmentDueDateExtensions', async (assignmentID, {dispatch, rejectWithValue}) => {
try{
const response = await API.get(`/api/assignments/${assignmentID}/due-day-extensions`);
return response.data.data as DueDateExtension[];
}catch(e){
dispatch(pushErrorNotification(e.data));
return rejectWithValue();
}
});
// reducer
const selectedAssignmentSlice = createSlice({
name: 'selectedAssignment',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(
getAssignmentAndDocuments.fulfilled,
((state, {payload}) => {
// assignmentDetail is everything but documents; documents kept on separate key in state obj to keep things simpler.
const {course, documents, due_at, id, released_at, set_key, show_after_due, title, weight} = payload;
state.assignment = {course, due_at, id, released_at, set_key, show_after_due, title, weight};
state.documents = documents;
})
)
.addCase(listAssignmentDueDateExtensions.fulfilled, ((state, {payload}) => {
state.dueDateExtensions = payload;
}))
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您将要介绍该应用程序以找到您的真正瓶颈。不鼓励编写完美的缓存代码并记住所有内容,因为这是影响开发人员生产力可维护性/错误等的过早优化。
随着这些功能,尽最大努力尽力而为。
这是关于在状态下放置的内容的好阅读,并尝试将“派生数据”(如计算)遗漏。 https://redux.js.org/usage/usage/deriving-data-selectors
这是关于
使用elector
有效的很好的阅读:https://react-react-react-react-react-react-reac.js.s.org/api/hooks#performance
我要樱桃选择一些我认为适用的东西:
使用Elector
在每个动作后运行。如果返回的响应与最后结果的参考比较有所不同,则将迫使组件重新渲染。您的
使用elector
呼叫正在返回状态对象。状态对象将因每个动作的参考平等而有所不同,因此它将始终迫使重新渲染。制作一个返回状态树中每个项目的选择器。假设您不是创建新对象,除非状态的那一部分发生变化,否则这些选择器不会默认情况下引起重新租户。
例如,浸入/redux工具包将保留不变的状态树部分。您也应该尝试使用标准REDUX,例如返回旧状态,除非还原器在该状态下行动。
那可能是最大的。您应该准确调查是什么原因导致了重新租赁的原因,在基本使用者优化(不要返回整个状态树),然后开始寻找适当的解决方案。
我认为这不好。那不是很多。一个Redux工具包异步动作将发射3个动作。
是的,我认为您需要肯定要注意,但请再次谨慎对待过早优化。我会试图确保没有犯重大错误(例如不依靠使用默认参考检查),但除此之外,没有更多的优化。为经过验证的瓶颈留出代码复杂性的空间。
You would want to profile the application to find your real bottlenecks. It's not encouraged to write perfectly cached code for example and memoize everything, as that's premature optimization that affects developer productivity maintainability/bugs etc.
With that out of the way, it does make sense to try our best to code things performantly.
This is a good read on what to put in state, and try to leave out "derived data" like calculations. https://redux.js.org/usage/deriving-data-selectors
This is a good read on how
useSelector
works:https://react-redux.js.org/api/hooks#performance
I'm going to cherry pick some stuff that I think applies:
useSelector
runs after every action. If the returned response differs in reference comparison to the last result, it will force the component to re-render.Your
useSelector
call is returning the state object. The state object will differ by reference equality on every action, so it will always force a re-render.Make a selector that returns each item in the state tree. Assuming you aren't creating new objects unless that part of state changes, those selectors won't cause re-renders by default.
Immer/Redux toolkit for example would preserve unchanged state tree portions. You should try to with standard redux as well, e.g. return old state unless reducer is acting on that state.
That's probably the big one. You should investigate exactly what is causing the re-renders, after the basic useSelector optimization (don't return whole state tree), and start looking for appropriate solutions.
I don't think it's bad. That's not a lot. One redux toolkit async action will fire 3 actions.
Yeah, I think you need to keep an eye out for sure but again careful about premature optimization. I'd be trying to make sure no major mistakes are being made (like not leaning on useSelector default reference checks) but no more optimization beyond that. Leave room for code complexity for the proven bottlenecks.