[英]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:两件事情:
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
,因此它将在每次渲染时重新创建。
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.