首頁  >  問答  >  主體

防止子元件對父元件陣列的更新所引起的無限重新渲染

在學生(兒童)組件中

在學生(家長)組件中

問題/疑問

請參閱此處的程式碼: 我是 CodeSandBox 連結

Student.tsx(兒童)

import React, { useState, useEffect, useRef } from "react";
import TextField from "@mui/material/TextField";

interface student {
  firstName: string;
  lastName: string;
  grade: number;
}

interface studentProps {
  id: number;
  firstName: string;
  lastName: string;
  grade: number;
  handleStudentsChange: (index: number, student: student) => void;
}

function Student(props: studentProps) {
  const [firstName, setFirstName] = useState(props.firstName);
  const [lastName, setLastName] = useState(props.lastName);
  const [grade, setGrade] = useState(props.grade);

  useEffect(() => {
    handleStudentsChange(id, {
      firstName: firstName,
      lastName: lastName,
      grade: grade
    });
  }, [firstName, lastName, grade, props]);

  return (
    <>
      <TextField
        label="firstName"
        onChange={(event) => setFirstName(event.target.value)}
        value={firstName}
      />
      <TextField
        label="lastName"
        onChange={(event) => setLastName(event.target.value)}
        value={lastName}
      />
      <TextField
        label="grade"
        onChange={(event) => setGrade(+event.target.value)}
        value={grade}
      />
    </>
  );

Students.tsx(父級)

import React, { useState, useCallback } from "react";
import Student from "./Student";

interface student {
  firstName: string;
  lastName: string;
  grade: number;
}

export default function Students() {
  const [students, setStudents] = useState<student[]>([
    { firstName: "Justin", lastName: "Bieber", grade: 100 },
    { firstName: "Robert", lastName: "Oppenhiemer", grade: 100 }
  ]);

  const handleStudentsChange = useCallback(
    (index: number, updatedStudent: student) => {
      // console.log(index) //I only want this to rerender when the value change however it turn into an infinity loop
      setStudents((prevStudents) => {
        const updatedStudents = [...prevStudents];
        updatedStudents[index] = updatedStudent;
        return updatedStudents;
      });
    },
    []
  );

  return (
    <>
      {students.map((student, index) => {
        return (
          <Student
            key={index}
            id={index}
            firstName={student.firstName}
            lastName={student.lastName}
            grade={student.grade}
            handleStudentsChange={(index: number, newStudent: student) =>
              handleStudentsChange(index, newStudent)
            }
          />
        );
      })}
    </>
  );
}

如上面的程式碼所示,我嘗試在學生(兒童)元件上使用React.memo ,並在handleStudentsChange 上使用useCallback ,希望能夠防止無限循環。然而,無限循環仍在持續。

P粉197639753P粉197639753208 天前419

全部回覆(1)我來回復

  • P粉955063662

    P粉9550636622024-02-27 00:32:58

    問題

    handleStudentsChange不僅在發生更改時無限運行一次-它從第一次渲染開始就無限運行。這是因為Student元件具有呼叫handleStudentsChangeuseEffect,它更新了Students元件中的狀態,導致Student元件重新渲染,然後再次呼叫useEffect,無限循環。

    解決方案

    您需要在更新輸入後才呼叫handleStudentsChange,而不是每次渲染後都會呼叫。我在下面的範例中包含了一個範例,它在從輸入觸發blur事件後更新了Students中的狀態。對於更聰明(和複雜)的方法,您可以對比props和state來決定是否需要更新,但我將讓您自己解決。

    const { Fragment, StrictMode, useCallback, useEffect, useState } = React;
    const { createRoot } = ReactDOM;
    const { TextField } = MaterialUI;
    
    函數學生(道具){
      const [firstName, setFirstName] = useState(props.firstName);
      const [lastName, setLastName] = useState(props.lastName);
      const [等級,setGrade] = useState(props.grade);
      const handleStudentsChange = props.handleStudentsChange;
      
      const onBlur = () =>; {
        handleStudentsChange(props.id, {
          名,
          姓,
          年級,
        });
      };
    
      返回 (
        <片段>
          <文字字段
            標籤=“名字”
            onBlur={onBlur}
            onChange={(事件)=>; setFirstName(event.target.value)}
            值={名字}
          >>
          <文字字段
            標籤=“姓氏”
            onBlur={onBlur}
            onChange={(事件)=>; setLastName(event.target.value)}
            值={姓氏}
          >>
          <文字字段
            標籤=“等級”
            onBlur={onBlur}
            onChange={(事件)=>; setGrade( event.target.value)}
            值={等級}
          >>
        </片段>
      );
    }
    
    函數學生() {
      const [學生,setStudents] = useState([
        { 名字:“賈斯汀”,姓氏:“比伯”,成績:100 },
        { 名字:“羅伯特”,姓氏:“奧本希默”,成績:100 }
      ]);
    
      const handleStudentsChange = useCallback(
        (索引,更新學生)=> {
          // console.log(index) // 我只希望它在值更改時重新渲染,但它會變成無限循環
          
          console.log({ updateStudent });
    
          setStudents((prevStudents) => {
            const UpdatedStudents = [...prevStudents];
            更新的學生[索引] = 更新的學生;
            返回更新的學生;
          });
        },
        []
      );
    
      返回 (
        <片段>
          {students.map((學生, 索引) => {
            返回 (
              <學生
                鍵={索引}
                id={索引}
                名字={學生.名字}
                姓氏={學生.姓氏}
                年級={學生.年級}
                handleStudentsChange={(index, newStudent) =>;
                  處理StudentsChange(索引,newStudent)
                }
              >>
            );
          })}
        </片段>
      );
    }
    
    函數應用程式(){
      返回 (
        
    <學生>>
    ); } const root = createRoot(document.getElementById("root")); root.render();
    <腳本跨域 src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <腳本跨域 src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <腳本跨域 src="https://unpkg.com/@mui/material@latest/umd/material-ui.production.min.js"></script>
    

    回覆
    0
  • 取消回覆