我的第一步是进行更改createItem
,使 Item 成为其唯一的功能组件。这意味着我可以记住该<Item/>
组件,以便它仅在道具更改时才会呈现,重要的是消息和键/索引(就像您之前呈现的值一样)。
这个https://codesandbox.io/s/funny-chaplygin-pvfoj的工作示例。
const Item = React.memo(({ message, index }) => {
console.log("Rendering:", index, message);
const calculatedMessage = msgSub(message);
return (
<div>
<strong>{"Message " + (1 + index)}: </strong>
{message} =>{" "}
<span
id={"preview-" + index}
dangerouslySetInnerHTML={{ __html: calculatedMessage }}
/>
</div>
);
});
const msgSub = message => {
// expensive function 2
const messageSub = message.replace(/[a]/g, "A").replace(/[e]/g, "E");
console.log("in msgSub:", message, messageSub);
return messageSub;
};
您可以看到,在初始渲染时,它会渲染所有项目,message
尤其是apple
两次。
如果两个组件相互独立渲染,并且碰巧使用相同的道具,这两个组件将被渲染。React.memo 不保存组件渲染。
它必须渲染 Item 组件两次<Item message="apple" />
,一次apple
在索引 0 处,另一次在索引 5 处apple
。
您会注意到我在 App 中放置了一个按钮,单击该按钮将更改数组的内容。
稍微更改原始数组,我将其放置carrot
在原始数组的索引 4 处,并将其移动到新数组中的索引 2,当我更新它时。
const [stringArray, setStringArray] = React.useState([
"apple",
"banana",
"cherry",
"durian",
"carrot",
"apple" // duplicate Apple
]);
const changeArray = () =>
setStringArray([
"apple",
"banana",
"carrot", // durian removed, carrot moved from index 4 to index 2
"dates", // new item
"elderberry", // new item
"apple"
]);
如果您查看控制台,您会在看到的第一个渲染中看到它,in msgSub: carrot cArrot
但是当我们更新数组时,in msgSub: carrot cArrot
会再次调用它。这是因为 key on<Item />
所以它被迫重新渲染。这是正确的,因为我们的键是基于索引的,并且胡萝卜改变了位置。但是,你说msgSub
的是一个昂贵的功能......
本地记忆
我注意到在你的阵列中你有apple
两次。
常量输入 = [“苹果”、“香蕉”、“樱桃”、“榴莲”、“接骨木”、“苹果”];
我觉得你想记住计算,这样apple
就不会再计算了,如果apple
以前计算过的话。
我们可以将计算的值存储在我们自己的记忆状态中,这样我们就可以查找消息值,看看我们之前是否计算过。
const [localMemoization, setLocalMemoization] = useState({});
我们希望确保在stringArray
更改时更新此 localMemoization。
React.useEffect(() => {
setLocalMemoization(prevState =>
stringArray.reduce((store, value) => {
const calculateValue =
prevState[value] ?? store[value] ?? msgSub(value);
return store[value] ? store : { ...store, [value]: calculateValue };
})
);
}, [stringArray]);
这条线const calculateValue = prevState[value] ?? store[value] ?? msgSub(value);
- 检查先前的状态以查看它是否具有先前的值(对于胡萝卜将第一个数组移动到第二个数组的情况)。
- 检查当前商店以查看它是否具有该值(对于第二个苹果案例)。
- 最后,如果什么都没看到,我们第一次使用昂贵的函数来计算值。
使用与之前渲染相同的逻辑,我们现在查找计算的值并将其传递给<Item>
React.memo,以便在 App 再次渲染时阻止该组件的重新渲染。
const output = stringArray.map((msg, key) => {
const expensiveMessage = localMemoization[msg];
return (
<Item
key={key}
message={msg}
calculatedValue={expensiveMessage}
index={key}
/>
);
});
此处的工作示例https://codesandbox.io/s/attempt-with-custom-hooks-and-usememo-y3lm5?file=/src/App.js:759-1007
由此,在控制台中,我们可以看到在第一次渲染时,apple
只计算了一次。
当数组改变时,我们不再计算carrot
,只计算改变的项dates
和elderberry
。