redux导致40+单页上的重新租户:想要帮助识别/修复问题

发布于 2025-02-08 05:45:31 字数 4879 浏览 0 评论 0原文

我正在为音乐理论应用程序公司的仪表板网站工作,用户可以在该网站上管理其信息,课程,作业,媒体内容等。

该网站是使用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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

樱花坊 2025-02-15 05:45:31

您将要介绍该应用程序以找到您的真正瓶颈。不鼓励编写完美的缓存代码并记住所有内容,因为这是影响开发人员生产力可维护性/错误等的过早优化。

随着这些功能,尽最大努力尽力而为。

这是关于在状态下放置的内容的好阅读,并尝试将“派生数据”(如计算)遗漏。 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,例如返回旧状态,除非还原器在该状态下行动。

那可能是最大的。您应该准确调查是什么原因导致了重新租赁的原因,在基本使用者优化(不要返回整个状态树),然后开始寻找适当的解决方案。

另外,在此特定页面上,我派遣6个不同的动作来更新3个单独的还原器?

是不好的做法吗?

我认为这不好。那不是很多。一个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.

Also, is it bad practice that on this particular page I dispatch 6 different actions that update 3 separate reducers?

I don't think it's bad. That's not a lot. One redux toolkit async action will fire 3 actions.

The pages load fairly quickly and are responsive, but I'm worried that all these re-renders are/will cause major issues.

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文