【问题标题】:Problem with re-renders in React with useCallback and useMemo使用 useCallback 和 useMemo 在 React 中重新渲染的问题
【发布时间】:2020-07-31 22:15:01
【问题描述】:

我有一个相当简单的组件示例 (Hello.js),它呈现三个组件,每个组件都有不同的 id (Speaker.js)。我有一个从 Speaker.js 传回的clickFunction。我认为当只有一个更改时,使用 React.memo 和 React.useCallback 会阻止所有三个重新渲染,但遗憾的是,您可以从 Speaker.js 中的 console.log 看到,单击三个按钮中的任何一个都会导致所有三个进行渲染。

这是stackblitz上的问题示例:

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

Hello.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

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>
  );
});

【问题讨论】:

    标签: reactjs react-hooks


    【解决方案1】:

    因为当您触发 clickFunction 时,它会更新扬声器,从而导致重新创建此功能,要解决此问题,您需要从 clickFunction 依赖项中删除 speakers 并从 setState 回调中访问它。 这里的解决方案:

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

    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>
      );
    };
    

    对于扬声器组件:

    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>
      );
    });
    

    【讨论】:

    • 谢谢@adel。感谢您抽出额外的时间回去改进答案。我第一次为你如何使它工作而苦苦挣扎,但第二次很容易,因为它只是 clickFunction 更改。
    • 这真的是我下一个问题的前奏,直到我得到这个问题我才能问这个问题。如果你有时间看它,它几乎是相同的。 stackoverflow.com/questions/63200464/…
    【解决方案2】:

    经过进一步思考,我认为我的回答可能并不完全正确:如果没有 [speakers] 依赖项,这将无法按预期工作。

    两件事:

    1. 传递给useCallback[speakers] 依赖项会导致函数在每次speakers 更改时重新创建,并且由于回调本身调用setSpeakers,它会在每次渲染时重新创建。

    2. 如果您修复 #1,扬声器组件将根本不会重新渲染,因为它们接收到相同的 speaker 属性。 speaker.favorite 已更改的事实不会触发重新渲染,因为 speaker 仍然是同一个对象。要解决此问题,请让您的 click 函数返回 rec 的副本,并翻转 favorite 而不是仅在现有对象中切换它:

    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>
      );
    };
    

    【讨论】:

    • 谢谢。很大的帮助。我仍然收到我不明白的警告:第 22:5 行:React Hook useCallback 缺少依赖项:'speakers'。要么包含它,要么移除依赖数组 react-hooks/exhaustive-deps overrideMethod @ react_devtools_backend.js:2273 printWarnings @ webpackHotDevClient.js:138 handleWarnings @ webpackHotDevClient.js:143 push../node_modules/react-dev-utils/webpackHotDevClient。 js.connection.onmessage@webpackHotDevClient.js:210.
    • 经过进一步思考,我认为我的回答可能并不完全正确:如果没有 [speakers] 依赖,这将无法按预期工作。
    • 检查我的答案,您可以从setState callback 访问当前状态,因此useCallback 不会对其进行中继,可以从依赖项中删除。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-27
    • 2021-11-11
    • 2022-01-19
    • 2019-11-18
    • 2020-01-25
    相关资源
    最近更新 更多