今天,我花了很多时间调试问题,每次重新渲染时都会在子组件中重置状态。在最终解决它之后,我意识到我不完全了解React功能组件和Array.map()是如何协同工作的,这就是为什么我希望有人可以阐明我遇到的问题:
比方说,我有一个ItemWrapper
组件,它返回了ItemListA
组件。 ItemListA
映射到数组,并返回Item
组件的列表。每个Item
组件都有其自己的状态,这些状态会随着某些操作而改变。
我刚开始的方式:
const ItemWrapper = ({ items }) => {
const [someState, setSomeState] = useState(null);
const someFunction = value => setSomeState(value);
...
const ItemListA = () => items.map(item =>
<Item
key={item.id}
item={item}
callback={someFunction}
/>
)
...
return (
<div>
<ItemListA />
</div>
);
};
问题:每当someFunction
子级之一调用Item
回调时,这会导致ItemWrapper
重新呈现并重置所有其他{{1 }}孩子。
通过将项目列表存储在局部变量而不是组件中来解决该问题:
Item
我不觉得我完全了解这里发生的事情。我的猜测是,以某种方式将项目列表存储在功能组件中,这样const ItemWrapper = ({ items }) => {
const [someState, setSomeState] = useState(null);
const someFunction = value => setSomeState(value);
...
const itemListB = items.map(item =>
<Item
key={item.id}
item={item}
callback={someFunction}
/>
);
...
return (
<div>
{itemListB}
</div>
);
};
组件及其子代(触发回调的子代除外-不知道为什么)被破坏,然后在任何时候重建ItemListA
组件被重新渲染,这意味着它们先前状态没有任何痕迹。是因为ItemWrapper
是一个函数,并且每次重新渲染都会创建一个新引用吗?
答案 0 :(得分:3)
是因为ItemListA是一个函数,并且每次重新渲染都会创建一个新引用吗?
是的,差不多。
在确定对DOM进行什么更改时,react将之前的虚拟dom与之后的虚拟dom进行比较,并查找更改。进行详尽的比较会很昂贵,因此他们做出了一些假设来加快速度。这些假设之一是,如果组件的类型已更改,则假定整个子树都已更改。 (有关更多信息,请参见react的article on reconciliation)
因此,在这种情况下,react第一次看到ItemListA
,第二次看到ItemListA
,它们是不同的组件类型。它们看起来与我们的眼睛非常相似,但是它们是不同的参考,这意味着它们的反应不同。因此,必须先卸载旧的并重新安装新的。
使用第二个代码,您不会在每个渲染上都创建一种新类型的组件,而只是创建一个其中包含元素的数组。这两个数组是不同的引用,但这没关系,因为它不是一种组件。