为什么我的状态挂钩没有正确更新?

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,函数也不会依赖于 itemsInPlaycurrent 绑定在闭包中。

解决它的另一种方法是更改​​ 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.
);