【问题标题】:Why is the state not updating correctly?为什么状态没有正确更新?
【发布时间】:2021-06-07 14:56:48
【问题描述】:

我正在构建一个聊天应用程序。现在我遇到的问题是,当我单击一个按钮时,handleMsgSend 按钮会运行,并向另一个客户端发送一个“newMsg”事件。收到此事件后,我希望 msgArr 状态会使用新收到的 msg 进行更新,但没有。里面全是新的味精,没有别的了。为什么会这样?我觉得我在学习 React 时跳过了技术内容。

export default function Chat (props) {
    const uuid = props.uuid;
    const classes = useStyles();
    const [open, setOpen] = useState(false);
    const [activeStatus, setActiveStatus] = useState('Offline');
    const [unreadMsgs, setUnreadMsgs] = useState(0);
    const [msgArr, setMsgArr] = useState([]);
    const chatBtnRef = useRef();
    const chatBoxRef = useRef();
    const msgInputRef = useRef();
    useEffect(() => {
        socket.emit('join', uuid);
        socket.emit('isOnline', uuid);
        socket.on('newMsg', msg => {
            setMsgArr([ ...msgArr, { type: 'received', msg }]);
            console.log(msgArr);
            if(!open) setUnreadMsgs(unreadMsgs + 1);
            chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight;
        });
        socket.on('isOnline', () => {
            setActiveStatus('Online');
            socket.emit('isOnline');
        });
        return () => {
            console.log('removed');
            socket.off('newMsg');
            socket.off('Online');
        }
    }, []);

    const handleMsgSend = e => {
        e.preventDefault();
        let msg = msgInputRef.current.value;
        setMsgArr([ ...msgArr, { type: 'sent', msg }]);
        e.currentTarget.reset();
        socket.emit('newMsg', {uuid,  msg});
        chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight;
    }
    const toggleChat = () => {
        setUnreadMsgs(0);
        if(open) setOpen(false);
        else setOpen(true);
    }

我如何呈现 msgArr:

                <div ref={ chatBoxRef } className={ classes.chatBox }>
                    {
                        msgArr.map((msgObj, index) => {
                            return (
                                <div key={index} className={`msg-container ${(msgObj.type == 'sent')? 'myMsg' : 'hisMsg'}`}>
                                    <span className='msg'>
                                        { msgObj.msg }
                                    </span>
                                </div>
                            )
                        })
                    }                     
                </div>

【问题讨论】:

  • 如果你依赖setMsgArr([ ...msgArr... 正下方的console.log(msgArr); 来告诉你状态数组中有什么,那么它会给你错误的信息,因为setMsgArr([ ...msgArr, { type: 'received', msg }]); 是一个异步调用,你应该'不要期望状态变量立即改变。
  • 正如@codemonkey 已经指出的那样,状态更新不会立即反映,因为它们是异步的并且受闭包的影响,请查看this post 了解更多详情
  • 好的,但至少它应该显示几个以前的消息,但它没有。似乎整个 msgArr 在“newMsg”触发后被重置
  • @ShubhamKhatri 这个问题简直要了我的命。
  • @nikeshlepz 你如何使用msgArr 进行渲染

标签: javascript node.js reactjs socket.io


【解决方案1】:

您的套接字侦听器附加在 useEffect 内,该 useEffect 仅在初始渲染时执行,因此您在其中看到的 msgArr 的值将始终引用 state 中定义的初始值,因为关闭。

现在,由于 msgArr 是在套接字侦听器的闭包中使用的,因此即使您收到新消息,您也将始终引用初始状态而不是更新状态。

要解决这个问题,你必须使用callback 方法来更新状态

   socket.on('newMsg', msg => {
        setMsgArr(prevMsgArr => [ ...prevMsgArr, { type: 'received', msg }]);
        if(!open) setUnreadMsgs(unreadMsgs + 1);
        chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight;
    }); 

一旦你这样做了,你将能够在下一个渲染周期中看到状态的更新值,从而能够基于数组渲染更新的内容

附:请注意,紧跟在setMsgArr 之后的console.log(msgArr); 不会为您提供更新的值,因为状态更新是异步的并且受关闭影响。请参阅 this post 了解更多信息。

【讨论】:

  • 非常有趣。一直认为只有在设置状态变量之前实际改变状态变量时才需要该回调。老实说,setMsgArr([ ...msgArr, { type: 'received', msg }]); 将继续重用同一个变量,这让我有点惊讶。我的意思是,它不会总是引用const [msgArr, setMsgArr] = useState([]); 会更新吗?无论如何,回调正常工作。
  • 好的,我确信正在使用初始渲染的 msgArr。 “......即使您收到新消息,您也将始终引用初始状态而不是更新的状态......”,我能够收到新的消息,只是所有以前的消息都丢失了。经过一段时间的试验,我发现,在组件重新渲染时,之前渲染的所有状态都将重置为它们初始化的值。因此,在触发“newMsg”事件时,只有一个 msg 被附加到空 msgArr 中。这是相当奇怪的行为。老实说,我必须深入研究它。
  • @nikeshlepz 我在回答中提到的回调方法是解决这种情况的方法,因为回调为您提供了最新状态
  • @codemonkey 问题在于,当调用 useEffect 函数时,它会从其封闭闭包中获取值,并且在再次调用之前永远不会更新它,这就是为什么你总是参考初始状态。
  • 非常有意义。我通过在useEffect 中执行addEventListener 在沙盒中对其进行了测试。这种行为一直存在,直到您更改为回调。就我而言,您的回答完美地解释了 OP 的问题并提供了一个很好的解决方案。
猜你喜欢
  • 2021-02-12
  • 2021-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-01
  • 2019-05-15
  • 1970-01-01
  • 2019-11-05
相关资源
最近更新 更多