React js将新对象推送到数组总是替换最后一个对象而不是插入

React js pushing new object to array always replace last object instead of insert

我有消息列表,并附加从套接字接收到的实时消息,一个从套接字插入的对象,但之后它总是替换最后一个而不是插入新的。

const [chatList, setChatList] = useState(props.chatList ? props.chatList : []);

useEffect(() => {

    const messageListener = (message) => {
        console.log('message',message);
        console.log('chatList',chatList);

        if(message.conversation_id == props.conversation_id){

                const updatedMsgs = [...chatList,message];
                setChatList(updatedMsgs);
        }
        
    };
  
    socket.on('myEventName', messageListener);

    return () => {
      socket.off('myEventName', messageListener);
    };
  }, [props.conversation_id]);

新消息和消息列表日志看起来像

看起来像是 chatList 状态的陈旧外壳。使用功能状态更新从以前的状态更新,而不是在回调范围内关闭的状态值更新,setChatList(list => [...list, message]);

useEffect(() => {
  const messageListener = (message) => {
    console.log('message',message);
    console.log('chatList',chatList);

    if (message.conversation_id == props.conversation_id) {
      setChatList(list => [...list, message]);
    } 
  };

  socket.on('myEventName', messageListener);

  return () => {
    socket.off('myEventName', messageListener);
  };
}, [props.conversation_id]);

由于 Javascript 中的 Stale Closures,确实会出现此问题。

Hooks heavily rely on JavaScript closures. That's why hooks are so expressive and simple. But closures are sometimes tricky.

示例如下:

function DelayedCount() {
  const [count, setCount] = useState(0);
  function handleClickAsync() {
    setTimeout(function delay() {
      setCount(count + 1);
    }, 1000);
  }
  return (
    <div>
      {count}
      <button onClick={handleClickAsync}>Increase async</button>
    </div>
  );
}

如果您快速点击按钮 2 次,则计数只会增加 1,而不是 2。

在每次点击时 setTimeout(delay, 1000) 安排在 1 秒后执行 delay()delay() 将变量 count 捕获为 0。

两者delay() closures(因为点击了2次)将状态更新为相同的值:setCount(count + 1) = setCount(0 + 1) = setCount(1).

都是因为第二次点击的 delay() closure 捕获了过时的计数变量为 0。

要解决这个问题,我们需要使用函数式方法 setCount(count => count + 1) 来更新计数状态:

...
function handleClickAsync() {
  setTimeout(function delay() {
    setCount(count => count + 1);
  }, 1000);
}
...

现在按钮确实按预期工作了。

这是完整的文章,可以帮助您了解 Stale ClosuresArticle Link