[英]How to rewrite this simple component tree to take advantage of React.memo?
在下面的代碼沙箱中,我們有一個<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>
);
}
如您所見, <Child />
組件使用React.memo來防止重新渲染。
但是,當組件樹更改時不會阻止重新渲染(這是有道理的)。 如果shouldWrapChildComponent
為true
,則<Child />
將在<div />
內部渲染,否則<Child />
將在<div />
用於渲染的地方渲染。 這就是我所說的“組件樹更改”的意思。
有沒有辦法重寫組件,使得組件樹不會改變,從而可以利用 React.memo? 或者,考慮到我們總是需要有條件地包裝<Child />
組件,有沒有辦法讓 React 保留<Child />
的渲染輸出?
但是,當組件樹更改時不會阻止重新渲染(這是有道理的)。 如果 shouldWrapChildComponent 為 true,則將在 a 內部進行渲染,否則將在用於渲染的位置進行渲染。 這就是我所說的“組件樹更改”的意思
再說說上面的引文,我們做個小改動,去掉div
元素:
{[0, 1, 2, 3, 4, 5].map((n) => {
return shouldWrapChildComponent ? (
<Child n={n} />
) : (
<Child n={n} />
);
})
}
結果是:即使在切換按鈕后,您的Child
組件也只會重新渲染 6 次。 您可以在App
組件上放置一個 console.log() 並檢查控制台,切換按鈕將導致整個App
重新渲染,但Child
組件將不再重新渲染。
因此,記憶工作正常並防止子組件額外重新渲染。
但是當你用另一個元素(這里用div
)包裝你的Child
組件時,問題就出現了,為什么?
為了檢查這種行為,我通過創建一個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>
);
}
當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
當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
如您所見,結果完全不同,因此每次在 DOM 中繪制新元素時,react 都會觸發重新渲染。
當比較兩棵樹時,React 首先比較兩個根元素。 行為因根元素的類型而異。
因此,隨着元素type
的改變, Reconciliation
算法會標記它並嘗試重新渲染它,這種重新渲染就像組件安裝之前的第一次渲染一樣(並導致額外的重新渲染與您的子元素)。
更多關於 React 官方文檔。
有很多解決方案可以避免這種額外的重新渲染,但作為一個簡單的解決方案,您可以更改主要部分,如下所示:
{[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.