对于这个问题,我在这里尝试验证我的理解是否正确。想象一下黑客新闻评论部分,其中有深度嵌套/线程评论——即树状结构——可以在树的任何级别/深度加载更多评论。我试图尽可能地防止重新渲染,所以我使用备忘录。
我无法判断的是,每次加载 10 个新的顶级组件时,现有的顶级注释是否会重新渲染,或者 memo 是否会阻止它们重新渲染?
假设我将“回复”加载到深度为 4 的现有评论中。通过备忘录,我希望只有包含此新回复的顶级评论和其余组件不会重新渲染。我担心的是设置新的 useState 的行为会导致整个树在添加新回复后重新渲染。
我构建这棵树的方式是将页面的所有数据存储在树的多个级别上不断更新的状态中,从而能够防止所有 TreeNode 组件重新渲染?
import React, { useState, memo } from "react";
function getChildrenLengthSum(tree) {
// Base case: if there's no valid tree or children property, return 0
if (!tree || !Array.isArray(tree.children)) {
return 0;
}
// Get the length of the current node's children
const currentLength = tree.children.length;
// Recursively get the sum of lengths from all child nodes
const childLengthsSum = tree.children.reduce(
(sum, child) => sum + getChildrenLengthSum(child),
0
);
// Return the sum of the current length and all child node lengths
return currentLength + childLengthsSum;
}
// Memoized TreeNode
const TreeNode = memo(
({ node, onUpdate }) => {
console.log(`Rendering Node ${node.id}`);
const handleAddChild = () => {
const newChild = { id: Math.random(), children: [] };
onUpdate(node.id, newChild);
};
return (
<div>
<div>
Node: {node.id} <button onClick={handleAddChild}>Add Child</button>
</div>
<div style={{ paddingLeft: 20 }}>
{node.children.map((child) => (
<TreeNode key={child.id} node={child} onUpdate={onUpdate} />
))}
</div>
</div>
);
},
(prevProps, nextProps) => {
// Compare children references directly
return prevProps.node === nextProps.node;
}
);
function App() {
const [tree, setTree] = useState([
{
id: 1,
children: [
{
id: 2,
children: [{ id: 4, children: [] }],
},
{ id: 3, children: [] },
],
},
]);
// Update handler for the tree
const handleUpdate = (parentId, newChild) => {
const updateTree = (nodes) =>
nodes.map((node) =>
node.id === parentId
? { ...node, children: [...node.children, newChild] } // New array for children
: { ...node, children: updateTree(node.children) }
);
setTree((prevTree) => updateTree(prevTree));
};
return (
<div>
{tree.map((rootNode) => (
<TreeNode key={rootNode.id} node={rootNode} onUpdate={handleUpdate} />
))}
</div>
);
}
export default App;
我无法判断是否每次加载10个新的顶级 组件,现有的顶级注释会重新渲染还是会 备忘录阻止它们重新渲染?
是的,会的。你的代码显示相同。这意味着: memo 可以让您在组件的 props 未更改时跳过重新渲染组件 (来自文档)
让我们检查代码中的控制台日志条目。
情况1:初始渲染后,让我们添加一个新节点作为父节点的子节点,节点:1。
日志
// Rendering Node 1
// Rendering Node 0.3296669083837116
渲染了两次。
首先是 id 为 1 的父节点,其次是新添加的节点。这两个渲染的原因可能是不言自明的,仍记录在下面以供澄清。
为了避免对象突变,通过复制现有数据来新创建父对象。下面的代码做了同样的事情。最终会重新渲染。
...
{ ...node, children: [...node.children, newChild] }
...
新节点也需要初始渲染。
其余 3 个节点 id:2、id:3 和 id:4 完好无损。扩展操作 ...node.children 恢复相同的对象。因此,这些渲染将被跳过。
案例2:初始渲染后,让我们添加一个新节点作为父节点的子节点,节点:4。
日志
// Rendering Node 1
// Rendering Node 2
// Rendering Node 4
// Rendering Node 0.8119218330034577
// Rendering Node 3
渲染了5次。这意味着整个树已经与新节点一起再次渲染。
原因是一样的。父节点 id:1 及其整个子节点是随着新节点的添加而新创建的。下面的代码完成了这项工作。
...
? { ...node, children: [...node.children, newChild] } // New array for children
: { ...node, children: updateTree(node.children) }
...
...我担心的是设置新的 useState 的行为会导致 添加新回复后重新渲染整个树。
在使用嵌套状态时,添加叶节点确实需要最大程度的重新渲染。然而,随着添加接近根,重新渲染的次数会减少。我们在上面验证的两个案例中也看到了同样的情况。
这是我构建这棵树的方式,其中包含页面的所有数据 存储在多个级别上不断更新的状态 树能够防止所有 TreeNode 组件重新渲染吗?
嵌套状态始终需要级联更新和重新渲染。因此,建议避免深度嵌套状态。相反,请考虑将其设为扁平。您可以在这里阅读更多内容避免深度嵌套状态