我创建了一个最小的代码片段以显示该问题,如下所示。
const PlayArea = (props) => {
const [itemsInPlay, setItemsInPlay] = useState([
{id: 'a'},
{id: 'b'}
]);
const onItemDrop = (droppedItem) => {
setItemsInPlay([...itemsInPlay, droppedItem]);
};
return (
<>
<Dropzone onDrop={onItemDrop} />
<div>
{itemsInPlay.map(item => (
<span
key={item.id}
/>
))}
</div>
</>
);
};
放置区检测到放置事件并调用onItemDrop
。但是,由于我不了解的原因,我只能放入一件物品。我放置的第一个项目正确地附加到itemsInPlay
上,并且除前两个之外还正确地重新渲染了第三个范围。
但是,我放下的所有后续项代替了第三项,而不是被附加。好像onItemDrop
已存储了对itemsInPlay
的引用,该引用已被初始值冻结。为什么会这样呢?应该在重新渲染时使用新值更新它,不是吗?
答案 0 :(得分:2)
Dropzone在最初呈现组件时仅设置一次其订阅令牌。发生这种情况时,传递给setSubscriptionToken
的回调包含onCardDrop
道具的 stale 值-由于添加了订阅,当组件重新呈现时,它将不会自动更新只有一次。
每次onCardDrop
更改时,您可以使用useEffect
退订并重新订阅,也可以使用setItemsInPlay
的回调形式:
const onItemDrop = (droppedItem) => {
setItemsInPlay(items => [...items, droppedItem]);
};
这样,即使旧版本的onItemDrop
被传递了,该函数也不会依赖于闭包中itemsInPlay
的 current 绑定。 / p>
另一种解决方法是更改Dropzone
,以便它不仅订阅一次,而且每次 onCardDrop
都进行订阅(并在订阅结束时退订)呈现),带有useEffect
和一个依赖项数组。
无论您做什么,都最好在卸载PlayArea组件后取消订阅,例如:
const [subscriptionToken, setSubscriptionToken] = useState<string | null>(null);
useEffect(
() => {
const callback = (topic: string, dropData: DropEventData) => {
if (wasEventInsideRect(dropData.mouseUpEvent, dropZoneRef.current)) {
onCardDrop(dropData.card);
setDroppedCard(dropData.card);
}
};
setSubscriptionToken(PubSub.subscribe('CARD_DROP', callback));
return () => {
// Here, unsubscribe from the CARD_DROP somehow,
// perhaps using `callback` or the subscription token
};
},
[] // run main function once, on mount. run returned function on unmount.
);