React.Memo与UseCallback一起使用
据我所知,react.memo是一种记忆组件的API:如果其道具不更改,则React使用该组件的最新渲染,而无需将其与以前的版本进行比较。跳过新的渲染并与旧的One加速应用程序进行比较。凉爽的。
现在,这是我没有得到的:如果道具不变,那么一个未回忆的组件也不会重新渲染,正如我从该简单代码(使用此链接查看演示,代码,该链接此页面上的摘要有点令人困惑):正常组件+usecallback和一个回忆的一个+usecallback之间的渲染数没有区别。基本上,我需要的是用户验证,因为普通组件不会通过相同的道具重新渲染。 那我想念什么?何时备忘录来优化?
const { useCallback, useEffect, useState, memo, useRef } = React;
function Child({ fn, txt }) {
const [state, setState] = useState(0);
console.log(txt + " rendered!");
useEffect(() => {
setState((state) => state + 1);
}, [fn]);
return (
<div style={{ border: "solid" }}>
I'm a Child
{!fn && <div>And I got no prop</div>}
{fn && <div>And I got a fn as a prop</div>}
<div>
and I've got rendered <strong>{state}</strong> times
</div>
</div>
);
}
const MemoChild = memo(Child);
function App() {
const [, setState] = useState(true);
const handlerOfWhoKnows = () => {};
return (
<div className="App">
I'm the parent
<br />
<button onClick={() => setState((state) => !state)}>
Change parent state
</button>
<h3>Ref</h3>
ref: <Child txt="ref" fn={handlerOfWhoKnows} />
<h3>test</h3>
useCB: <Child txt="useCb" fn={useCallback(handlerOfWhoKnows, [])} />
memo: <MemoChild txt="memo" fn={handlerOfWhoKnows} />
memo + useCB: <MemoChild txt="memo+useCb" fn={useCallback(handlerOfWhoKnows, [])} />
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
该示例中有几件事,但让我们从标题开始:您的孩子标记为
“ Memo+USECB”
是完全正确的,它显示了正确的方式(或者至少, a 正确的方式)避免了不必要的组件重新渲染。它在做兼有必要的事情:纪念组件(通过
Memo
)和
确保组件的道具不会不必要地更改(通过
usecallback
)您的其他设备,将其标记为
“ usecb”
和“ memo”
,每个标记为 缺少一半必要的不可能:“ usecb”
没有记忆,因此每次父母呈现时,它都会重新呈现“ usecb”
没有记忆,因此每当父派渲染“ memo”
获得不同的fn
每次支架,因此每次父母呈现时都会重新呈现
它都会重新呈现组合的记忆和确保这些道具不会更改(通过
USECALLBACK
或usememo
或useref
等),以避免不必要的重新订阅者。他们一个人都不是,这是您的示例所显示的。关于这个示例可能会稍微误导您的一件事是,您的组件说“并且我已经渲染了
{state}
times”,但是state> state
ISN 't 计算渲染数的数量,它计算fn
prop更改值的次数,这不是同一件事。 “渲染”是对您功能组件函数的调用(或class
component的Render
方法)。在您的示例中,渲染的数量由“ USECB渲染!”显示。和“备忘录渲染!”消息,您会看到父母呈现的每个时间,因为我们单击按钮。您已经说过(您的重点):
您的示例表明您是未遗忘的孩子 do 即使它的道具不变,也会重新渲染:
“ usecb”
版本具有稳定的道具,但是每次父母呈现时,它仍然会重新呈现。您可以看到这一点,因为它再次输出“ USECB渲染!”每次单击按钮时,导致父母重新渲染。这是您示例的更新版本,希望在A 渲染发生时更清楚地显示出更清晰的版本,而在发生Prop更改时,使用记录来进行渲染,以及组件的渲染输出进行Prop Change:
但是,很难清楚地看到日志记录,因此这是第二个版本,其中包括组件渲染的输出中的渲染数(在渲染结果中包含非状态是非常不寻常的,但有用的只是在这种情况下为插图的目的,查看发生了什么):
因此,您的示例确实显示了正确的方法,并使用
“ Memo+USECB”
Child。There are a couple of things going on in that example, but let's start with the headline: Your child labelled
"memo+useCb"
is exactly right, it's showing the correct way (or at least, a correct way) to avoid having a component re-render unnecessarily. It's doing both necessary things:Memoizing the component (via
memo
)and
Ensuring that the component's props don't change unnecessarily (via
useCallback
)Your others, the ones labelled
"useCb"
and"memo"
, each lack half of the necessary incredients:"useCb"
isn't memoized, so it re-renders every time the parent renders"memo"
is getting a differentfn
prop every time, so it re-renders every time the parent rendersIt's the combination of memoizing and ensuring the props don't change (via
useCallback
, oruseMemo
, oruseRef
, etc.) that avoids unnecessary re-renders. Either of them, alone, doesn't, which is what your example is showing.One thing about the example that may be misleading you slightly is that you have the components saying "and I've got rendered
{state}
times" butstate
isn't counting the number of renders, it's counting the number of times thefn
prop changed value, which is not the same thing. A "render" is a call to your function component's function (or therender
method of aclass
component). In your example, the number of renders is shown by the "useCb rendered!" and "memo rendered!" messages, which you see every time the parent renders because we click the button.You've said (your emphasis):
Your example is showing you that the un-memoized child does get re-rendered, even when its props don't change: the
"useCb"
version has stable props, but it still re-renders every time the parent renders. You can see that because, again, it outputs "useCb rendered!" every time you click the button causing the parent to re-render.Here's an updated version of your example hopefully showing more clearly when a render happens vs. when a prop change happens, using logging for renders and the component's rendered output for prop changes:
It can be hard to see the logging clearly, though, so here's a second version that includes the number of renders in the component's rendered output (it's very unusual to include non-state in the rendered result, but helpful just for the purposes of illustration in this case, to see what's going on):
So again, your example did show the right way to do it, with the
"memo+useCb"
child.除@TJ Crowder的答案外,
您的示例项目还以
&lt; strictmode&gt;
运行,因此React将在 dev 模式下两次运行一个组件来验证事物。要了解问题,首先让我们从index.js
暂时删除&lt; scrictmode
通过使用
备忘录
,您告诉对帐算法,如果道具相同,请不要下降并再次渲染子组件。但是在没有备忘录
的普通组件中,对帐算法将穿越树并调用您的子组件(因为那是算法应该做的)。一旦完成遍历(渲染),React将把更改与DOM冲洗。由于没有任何更改会对DOM进行。但是,使用备忘
您正在加快遍历过程。注:
在React中,'渲染'并不意味着更新DOM元素。渲染是指调用您的函数组件或component.render()方法。这不是一个昂贵的过程,因此React会这样做,但是React不会盲目地将变化冲洗给DOM。您可以通过打开检查元素来验证这一点。当您展开检查元素时,单击按钮,您将不会看到任何更改(创建/删除DOM元素上的紫色颜色亮点的动画)会发生。
通过使用备忘录,您可以手动停止渲染()并移至兄弟姐妹(如果有)继续重新渲染而不是进入子元素。这也是为什么您不能使用
console.log
实际测试React应用程序的性能。console.log
是副作用。严格地告诉您不要为渲染阶段(功能主体的最高级别)添加副作用。 您需要使用使用
。要处理副作用, 重新加载预览,我们将在控制台上获取以下日志,
对于此组件,
它将在MOUTE上登录
1。在坐骑上登录两次,
这是您看到每个日志打印两次的原因,您使用
pers a
使用>
在您的&lt; child/&gt;
组件中。由于触发了状态变化,因此第二次也将在第二次运行
&lt; child /&gt; < /code>。它与道具无关。如果我们删除此
useffect
,现在您可以将安装log看到为单击按钮。
2。在孩子中删除
strictmmode
stricteffect 后 ,然后单击按钮。这次将打印这次您可以看到的日志,
MEMO+USECB
未打印。下一个问题是为什么,3。为什么在第二步中呈现
备忘录!
。这是因为您没有回忆
fn
,因此每次渲染时都会重新创建。因此,道具变更 - &gt;组件重新渲染,因此,
随着更改,代码应该看起来像是
现在,当我们单击按钮时,我们只会看到
,所以您的问题的答案,
如果道具相同,备忘录有助于手动停止进一步的渲染。一旦看到
备忘录
,对帐算法将停止遍历,道具相同。它将完成渲染树,否则将继续渲染兄弟姐妹。同样,使用备忘录太多也可能导致性能问题,因为
备忘
它会做一些过程来弄清楚是否渲染孩子。使用usecallback
和usememo
仔细。Additional to the @T.J Crowder's answer,
Your sample project runs in
<StrictMode>
so React will run a component twice in dev mode to validate things. To understand the issue, first lets remove<ScrictMode
temporally from theindex.js
By using
memo
you are telling the reconciliation algorithm to NOT to go down and render the Child component again if the props are same. But in a normal component withoutmemo
, the reconciliation algorithm will traverse through the tree and call your child component ( because that's what algorithm supposed to do ). Once the traversing(rendering) is done, react will flush the changes to the DOM. As there's no changes will be committed to the DOM. but usingmemo
you are speed up the traversing process.NOTE:
In React, 'render' doesn't mean updating the DOM elements. rendering mean calling your function component or Component.render() method. This is not an expensive process so React will do it but React won't blindly flush the changes to the DOM. You can validate this by open the inspect element. When you expand the inspect element, click the button, you won't see any changes(create/delete animation with purple color highlight on dom elements) happens.
By using memo you manually stop the render() and move to the sibling element ( if available ) to continue re render instead of going into the child element. This is also why you cannot use
console.log
to actually test the performance of the react app.console.log
is a side effect. React strictly telling you to not add side effect to the render phase ( top level of your function body ). To handle side effects you need to useuseEffect
.Now lets see the issues in your code, Based on your sandbox code, after removing
<StrictMode>
and reload the preview, we will get the following logs on the console,For this component,
It will log following on mount,
1. log print twice on mount
The reason why you see each logs print twice is, you use a
useEffect
inside your<Child/>
component.Since the state change triggered, React will re run the
<Child />
for the 2nd time as well. It has nothing to do with the props. If we remove thisuseEffect
, now you can see the mount log as,2. Clicking the button
After remove the
StrictMode
anduseEffect
in the child, and click the button. This time the log will printThis time you can see,
memo+useCb
is not printed. The next issue is why,3. Why
memo rendered!
in 2nd step.This is because you didn't memoized the
fn
so it will recreate everytime when rendering. So props change -> component re renderSo code should be,
Now with the changes, the component will looks like,
Now when we click the button, we will only see,
So the answer for your question,
memo helps to manually stop the further rendering if the props are same. Reconciliation algorithm will stop traversing once it see a
memo
and props are same. It will complete the rendering the tree or it will continue to render the sibling element.Also using the memo too much can lead to performance issues as well because
memo
it self does some process to figure out whether to render the child or not. UseuseCallback
anduseMemo
carefully.