为什么我的状态挂钩没有正确更新?
Why isn't my state hook updating correctly?
我对我的代码进行了最少的切割以显示问题,如下所示。
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>
</>
);
};
dropzone 检测到 drop 事件并调用 onItemDrop
。但是,由于我不明白的原因,我只能放弃一项。我删除的第一个项目正确地附加到 itemsInPlay
,并且除了开始的两个之外,它还正确地重新呈现了第三个跨度。
但是,我删除的任何后续项目 都会替换第三个项目 而不是附加。就好像 onItemDrop
存储了对 itemsInPlay
的引用,它被初始值冻结了。为什么会这样?它应该在重新渲染时更新为新值,不是吗?
Dropzone 仅在最初呈现组件时设置其订阅令牌一次。发生这种情况时,传递给 setSubscriptionToken
的回调包含 stale 属性的 onCardDrop
值 - 它不会在组件 re-renders 时自动更新,因为订阅只添加了一次。
您可以在每次 onCardDrop
更改时取消订阅并重新订阅,使用 useEffect
,或者使用 setItemsInPlay
的回调形式:
const onItemDrop = (droppedItem) => {
setItemsInPlay(items => [...items, droppedItem]);
};
这样,即使传递了旧版本的 onItemDrop
,函数也不会依赖于 itemsInPlay
的 current 绑定在闭包中。
解决它的另一种方法是更改 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.
);
我对我的代码进行了最少的切割以显示问题,如下所示。
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>
</>
);
};
dropzone 检测到 drop 事件并调用 onItemDrop
。但是,由于我不明白的原因,我只能放弃一项。我删除的第一个项目正确地附加到 itemsInPlay
,并且除了开始的两个之外,它还正确地重新呈现了第三个跨度。
但是,我删除的任何后续项目 都会替换第三个项目 而不是附加。就好像 onItemDrop
存储了对 itemsInPlay
的引用,它被初始值冻结了。为什么会这样?它应该在重新渲染时更新为新值,不是吗?
Dropzone 仅在最初呈现组件时设置其订阅令牌一次。发生这种情况时,传递给 setSubscriptionToken
的回调包含 stale 属性的 onCardDrop
值 - 它不会在组件 re-renders 时自动更新,因为订阅只添加了一次。
您可以在每次 onCardDrop
更改时取消订阅并重新订阅,使用 useEffect
,或者使用 setItemsInPlay
的回调形式:
const onItemDrop = (droppedItem) => {
setItemsInPlay(items => [...items, droppedItem]);
};
这样,即使传递了旧版本的 onItemDrop
,函数也不会依赖于 itemsInPlay
的 current 绑定在闭包中。
解决它的另一种方法是更改 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.
);