简体   繁体   English

使用 useCallback 和 useMemo 在 React 中重新渲染的问题

[英]Problem with re-renders in React with useCallback and useMemo

I've got a fairly simple example of a component (Hello.js) that renders three components, each with a different id (Speaker.js).我有一个相当简单的组件示例 (Hello.js),它呈现三个组件,每个组件都有不同的 id (Speaker.js)。 I have a clickFunction that I pass back from the Speaker.js.我有一个从clickFunction传回的 clickFunction。 I would think that using React.memo and React.useCallback would stop all three from re-rendering when only one changes, but sadly, you can see from the console.log in Speaker.js, clicking any of the three buttons causes all three to render.我认为当只有一个更改时,使用 React.memo 和 React.useCallback 会阻止所有三个重新渲染,但遗憾的是,您可以从 Speaker.js 中的 console.log 看到,单击三个按钮中的任何一个都会导致所有三个渲染。

Here is the problem example on stackblitz:这是stackblitz上的问题示例:

https://stackblitz.com/edit/react-dmclqm https://stackblitz.com/edit/react-dmclqm

Hello.js你好.js

import React, { useCallback, useState } from "react";

import Speaker from "./Speaker";

export default () => {
  const speakersArray = [
    { name: "Crockford", id: 101, favorite: true },
    { name: "Gupta", id: 102, favorite: false },
    { name: "Ailes", id: 103, favorite: true },
  ];

  const [speakers, setSpeakers] = useState(speakersArray);

  const clickFunction = useCallback((speakerIdClicked) => {
    var speakersArrayUpdated = speakers.map((rec) => {
      if (rec.id === speakerIdClicked) {
        rec.favorite = !rec.favorite;
      }
      return rec;
    });
    setSpeakers(speakersArrayUpdated);
  },[speakers]);

  return (
    <div>
      {speakers.map((rec) => {
        return (
          <Speaker
            speaker={rec}
            key={rec.id}
            clickFunction={clickFunction}
          ></Speaker>
        );
      })}
    </div>
  );
};

Speaker.js扬声器.js

import React from "react";

export default React.memo(({ speaker, clickFunction }) => {
  console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);
  return (
    <button
      onClick={() => {
        clickFunction(speaker.id);
      }}
    >
      {speaker.name} {speaker.id} {speaker.favorite === true ? "true" : "false"}
    </button>
  );
});

because when you fire clickFunction it update speakers wich cause the recreating of this functions, to solve this you need to remove speakers from clickFunction dependencies and accessing it from setState callback.因为当您触发clickFunction时,它会更新扬声器,从而导致重新创建此函数,要解决此问题,您需要从clickFunction依赖项中删除speakers并从setState回调中访问它。 here the solution:这里的解决方案:

import React, { useCallback, useState,useEffect } from "react";从“react”导入反应,{ useCallback,useState,useEffect };

import Speaker from "./Speaker";

export default () => {
  const [speakers, setSpeakers] = useState([
    { name: "Crockford", id: 101, favorite: true },
    { name: "Gupta", id: 102, favorite: false },
    { name: "Ailes", id: 103, favorite: true },
  ]);

  const clickFunction = useCallback((speakerIdClicked) => {
    setSpeakers(currentState=>currentState.map((rec) => {
      if (rec.id === speakerIdClicked) {
        rec.favorite = !rec.favorite;
       return {...rec};
      }
     return rec
    }));
  },[]);
  useEffect(()=>{
    console.log("render")
  })

  return (
    <div>
      {speakers.map((rec) => {
        return (
          <Speaker
            speaker={rec}
            key={rec.id}
            clickFunction={clickFunction}
          ></Speaker>
        );
      })}
    </div>
  );
};

and for speaker component:对于扬声器组件:

import React from "react";

export default React.memo(({ speaker, clickFunction }) => {
  return (
    <button
      onClick={() => {
        clickFunction(speaker.id);
      }}
    >
      {speaker.name} {speaker.id} {speaker.favorite === true ? "true" : "false"}
    </button>
  );
});

Upon further reflection, I think my answer may not be entirely correct: without the [speakers] dependency this won't work as intended.经过进一步思考,我认为我的回答可能并不完全正确:如果没有[speakers]依赖,这将无法按预期工作。

Two things:两件事情:

  1. The [speakers] dependency passed to useCallback causes the function to get recreated every time speakers changes, and because the callback itself calls setSpeakers , it will get recreated on every render. 传递给 useCallback[speakers]依赖项会导致 function 在每次 speakers更改时重新创建,并且由于回调本身调用 setSpeakers ,因此它将在每次渲染时重新创建。

  2. If you fix #1, the Speaker components won't re-render at all, because they're receiving the same speaker prop.如果修复 #1,扬声器组件根本不会重新渲染,因为它们接收到相同的speaker道具。 The fact that speaker.favorite has changed doesn't trigger a re-render because speaker is still the same object. speaker.favorite已更改的事实不会触发重新渲染,因为speaker仍然是相同的 object。 To fix this, have your click function return a copy of rec with favorite flipped instead of just toggling it in the existing object:要解决此问题,请单击 function 返回带有favorite翻转的rec副本,而不是仅在现有 object 中切换它:

import React, { useCallback, useState } from "react";

import Speaker from "./Speaker";

export default () => {
  const speakersArray = [
    { name: "Crockford", id: 101, favorite: true },
    { name: "Gupta", id: 102, favorite: false },
    { name: "Ailes", id: 103, favorite: true },
  ];

  const [speakers, setSpeakers] = useState(speakersArray);

  const clickFunction = useCallback((speakerIdClicked) => {
    var speakersArrayUpdated = speakers.map((rec) => {
      if (rec.id === speakerIdClicked) {
        return { ...rec, favorite: !rec.favorite }; // <= return a copy of rec
      }
      return rec;
    });
    setSpeakers(speakersArrayUpdated);
  }, []); // <= remove speakers dependency

  return (
    <div>
      {speakers.map((rec) => {
        return (
          <Speaker
            speaker={rec}
            key={rec.id}
            clickFunction={clickFunction}
          ></Speaker>
        );
      })}
    </div>
  );
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM