[英]How to rewrite this simple component tree to take advantage of React.memo?
In the followingCode Sandbox we have a <Child />
component that either renders within a <div />
or not, depending on state.在下面的代码沙箱中,我们有一个<Child />
组件,它要么在<div />
呈现,要么不在,取决于状态。
import React, { useState, memo } from "react";
const Child = memo(
({ n }) => {
console.log("Re-rendered child");
return <span>Child {n}</span>;
},
() => true
);
export default function App() {
const [shouldWrapChildComponent, setShouldWrapChildComponent] = useState(
false
);
return (
<div>
<button
onClick={() =>
setShouldWrapChildComponent(
(shouldWrapChildComponent) => !shouldWrapChildComponent
)
}
>
Toggle Wrapper
</button>
<br />
{[0, 1, 2, 3, 4, 5].map((n) => {
return shouldWrapChildComponent ? (
<div>
<Child n={n} />
</div>
) : (
<Child n={n} />
);
})}
</div>
);
}
As you can see, the <Child />
component is using React.memo to prevent re-renders.如您所见, <Child />
组件使用React.memo来防止重新渲染。
However, re-renders are not prevented when the component tree changes (and this makes sense).但是,当组件树更改时不会阻止重新渲染(这是有道理的)。 If shouldWrapChildComponent
is true
, then <Child />
will render inside of a <div />
, and otherwise <Child />
will render where <div />
used to render.如果shouldWrapChildComponent
为true
,则<Child />
将在<div />
内部渲染,否则<Child />
将在<div />
用于渲染的地方渲染。 This is what I mean by "the component tree changes".这就是我所说的“组件树更改”的意思。
Is there a way to rewrite the components such that the component tree does not change , and therefore can take advantage of React.memo?有没有办法重写组件,使得组件树不会改变,从而可以利用 React.memo? Or, given that we'll always need conditionally wrap the <Child />
component, is there no way to have React preserve the rendered output of <Child />
?或者,考虑到我们总是需要有条件地包装<Child />
组件,有没有办法让 React 保留<Child />
的渲染输出?
However, re-renders are not prevented when the component tree changes (and this makes sense).但是,当组件树更改时不会阻止重新渲染(这是有道理的)。 If shouldWrapChildComponent is true, then will render inside of a , and otherwise will render where used to render.如果 shouldWrapChildComponent 为 true,则将在 a 内部进行渲染,否则将在用于渲染的位置进行渲染。 This is what I mean by "the component tree changes"这就是我所说的“组件树更改”的意思
Let's talk about the above quotation, let's do a small change and remove the div
element:再说说上面的引文,我们做个小改动,去掉div
元素:
{[0, 1, 2, 3, 4, 5].map((n) => {
return shouldWrapChildComponent ? (
<Child n={n} />
) : (
<Child n={n} />
);
})
}
The result is: your Child
component will re-render only 6 times even after toggling with the button.结果是:即使在切换按钮后,您的Child
组件也只会重新渲染 6 次。 You can put a console.log() on the App
component and check the console, the toggle button will cause a re-render for your whole App
but the Child
component will not re-render anymore.您可以在App
组件上放置一个 console.log() 并检查控制台,切换按钮将导致整个App
重新渲染,但Child
组件将不再重新渲染。
So, the memorization is working properly and prevents the child component from extra re-render.因此,记忆工作正常并防止子组件额外重新渲染。
But the problem appeared when you wrap your Child
component with another element ( here with div
), why?但是当你用另一个元素(这里用div
)包装你的Child
组件时,问题就出现了,为什么?
To check this behavior, I separate the main part from the return method by creating a resultArray
variable just to log the resultArray
before rendering.为了检查这种行为,我通过创建一个resultArray
变量来将主要部分与返回方法分开,以便在渲染之前记录resultArray
。
import React, { useState, memo } from "react";
const Child = memo(
({ n }) => {
console.log("Re-rendered child");
return <span>Child {n}</span>;
},
() => true
);
export default function App() {
const [shouldWrapChildComponent, setShouldWrapChildComponent] = useState(
false
);
const resultArray = [0, 1, 2, 3, 4, 5].map((n) => {
return shouldWrapChildComponent ? (
<div>
<Child n={n} />
</div>
) : (
<Child n={n} />
);
})
console.log(`When shouldWrapChildComponent is ${shouldWrapChildComponent} the result array is: `, resultArray);
return (
<div>
<button
onClick={() =>
setShouldWrapChildComponent(
(shouldWrapChildComponent) => !shouldWrapChildComponent
)
}
>
Toggle Wrapper
</button>
<br />
{resultArray}
</div>
);
}
The resultArray
when shouldWrapChildComponent
is false :当shouldWrapChildComponent
为false时的resultArray
:
>(6) [Object, Object, Object, Object, Object, Object]
>0: Object
type: "div" // pay attention here
key: null
ref: null
>props: Object
n: 0 // ----> passing n via props to Child
_owner: FiberNode
_store: Object
>1: Object
>2: Object
>3: Object
>4: Object
>5: Object
The resultArray
when shouldWrapChildComponent
is true :当shouldWrapChildComponent
为true时的resultArray
:
>(6) [Object, Object, Object, Object, Object, Object]
>0: Object
type: null // pay attention here
key: null
ref: null
>props: Object // -----> passing a object instead of Child
>children: Object
>type: Object
>type: ƒ _c() {}
>compare: f () {}
_owner: FiberNode
_store: Object
>1: Object
>2: Object
>3: Object
>4: Object
>5: Object
As you can see, the result is totally different, so react will trigger a re-render every time to draw the new elements in DOM.如您所见,结果完全不同,因此每次在 DOM 中绘制新元素时,react 都会触发重新渲染。
When diffing two trees, React first compares the two root elements.当比较两棵树时,React 首先比较两个根元素。 The behavior is different depending on the types of the root elements.行为因根元素的类型而异。
So, as the element type
gets changed, the Reconciliation
algorithm flagged it and tried to re-rendered it, this re-render is like the first render on your component before the component did mount (and cause extra re-render with your Child element).因此,随着元素type
的改变, Reconciliation
算法会标记它并尝试重新渲染它,这种重新渲染就像组件安装之前的第一次渲染一样(并导致额外的重新渲染与您的子元素)。
More on React official documentation .更多关于 React 官方文档。
There are many solutions to avoid this extra re-rendering, but as a simple solution you can change the main part as below:有很多解决方案可以避免这种额外的重新渲染,但作为一个简单的解决方案,您可以更改主要部分,如下所示:
{[0, 1, 2, 3, 4, 5].map((n) => {
return shouldWrapChildComponent ? (
<div>
<Child n={n} />
</div>
) : (
<div style={{display: "inline"}}>
<Child n={n} />
</div>
);
})
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.