在React中,为什么我的数组在由WebSocket消息触发的函数中被认为是空的?

xmq68pz9  于 2023-08-05  发布在  React
关注(0)|答案(3)|浏览(98)

在React中,我使用useState定义了一个数组变量,名为messages。我在页面上也有一个组件,它没有任何DOM,而是侦听WebSocket上的事件。当我的组件收到来自WebSocket的事件通知时,我的messages变量的长度总是零-尽管它之前有多个消息。为什么在输入函数时数组是空的?
下面是重现该问题的最小代码集:
WebSocketNotificationHandler.tsx

import { useEffect } from "react";
import config from "../../../config.json";
import { IChatMessage } from "../../../services/chatService";

interface WebSocketNotificationHandlerProps {
  onReceiveMessages: (newMessages: IChatMessage[]) => void;
}

export let client: WebSocket = new WebSocket(
  config.webSocketAddress,
  config.webSocketProtocol
);

const WebSocketNotificationHandler = (
  props: WebSocketNotificationHandlerProps
) => {
  useEffect(() => {
    if (client) {
      client.onmessage = async (ev) => {
        handleWebSocketMessage(ev);
      }
    }
  }, []);

  const handleWebSocketMessage = async (ev: MessageEvent<string>) => {
    props.onReceiveMessages([]); // Simplified, normally we send one or more
  }

  return (
    <></>
  );
}

export default WebSocketNotificationHandler;

字符串
GroupPage.tsx

import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import chatService, { IChatMessage, ICreateMessageResponse } from '../../../services/chatService';
import ChatMessage from '../../atoms/ChatMessage/ChatMessage';
import ChatStagingArea from '../../organisms/ChatStagingArea/ChatStagingArea';
import WebSocketNotificationHandler from '../../organisms/WebSocketNotificationHandler/WebSocketNotificationHandler';

const GroupPage = () => {
  const { groupName } = useParams();

  const [messages, setMessages] = useState<IChatMessage[]>([]);

  const loadGroup = async () => {
    setMessages([]);
    await chatService // REST API to get current messages, returns a list >0 elements
      .getGroup(groupName)
      .then(data => {
        setMessages([...data.data.messages]);
      });
  }

  useEffect(() => {
    loadGroup();
  }, []);

  const handleMessageSent = (message: ICreateMessageResponse, text: string) => {
    let newMessages = [...messages, { /* Message data for a new message */ }];
    setMessages(newMessages);
  }

  const handleNewMessages = (newMessages: IChatMessage[]) => {
    console.log(messages.length); // Always 0 even if I can see multiple???
    // Code goes here that adds the newMessages to the messages array
  };

  return (
    <>
      {messages && messages.map(m => <ChatMessage key={m.id} author={m.fullName} date={m.authoredDate} message={m.message} />)}

      <ChatStagingArea groupName={groupName} onSent={(message, text) => handleMessageSent(message, text)} />

      <WebSocketNotificationHandler onReceiveMessages={newMessages => handleNewMessages(newMessages)} />
    </>
  );
}

export default GroupPage;

iugsix8n

iugsix8n1#

问题

问题是handleNewMessages回调中的messages状态上的陈旧闭包,该回调在传递给WebSocketNotificationHandler的匿名函数中被调用为onReceiveMessages,其中它由实际示例化client.onmessage事件处理程序的handleWebSocketMessage调用。
初始为空的messages状态数组是在事件处理程序和回调被示例化时从初始呈现周期开始在作用域中关闭的对象。

溶液

如果您只是想附加发送的消息和接收的消息数组,那么您在GroupPage中的处理程序应该真正使用功能状态更新。函数状态更新将回调函数传递给状态更新器函数,状态更新器函数将传递当前状态值,然后计算并返回下一个状态值。这避免了在您尝试更新的状态值上出现陈旧闭包的问题。
示例如下:

const handleMessageSent = (message: ICreateMessageResponse, text: string) => {
  setMessages(messages => [...messages, { /* Message data for a new message */ }]);
  
  // or setMessages(messages => messages.concat({ /* Message data for a new message */ }));
};

const handleNewMessages = (newMessages: IChatMessage[]) => {
  setMessages(messages => [...messages, ...newMessages]);

  // or setMessages(messages => messages.concat(...newMessages));
};

字符串
如果你想控制台记录messages状态数组长度,那么使用一个单独的useEffect钩子来实现:

useEffect(() => {
  console.log(messages.length);
}, [messages]);


如果你 * 真的 * 必须在handleNewMessages中记录messages状态数组长度,那么使用React ref来缓存当前的messages状态值,以便在组件生命周期中的任何时候都可以访问ref的当前值。
示例如下:

const [messages, setMessages] = useState<IChatMessage[]>([]);

const messagesRef = useRef<IChatMessage[]>(messages);

useEffect(() => {
  messagesRef.current = messages;
}, [messages]);

...

const handleNewMessages = (newMessages: IChatMessage[]) => {
  console.log(messagesRef.current.length);

  setMessages(messages => messages.concat(...newMessages));
};

2ekbmq32

2ekbmq322#

根据我的理解,当执行handleWebSocketMessage函数时,它指向消息的初始状态,该状态是一个空数组,即使父GroupPage组件中的状态已更改。
您可以使用useCallback钩子来创建handleNewMessages函数的备忘录版本,这将确保该函数始终引用setMessages函数的最新版本。

const WebSocketNotificationHandler = (props: WebSocketNotificationHandlerProps) => {

    const handleWebSocketMessage = useCallback(async (ev: MessageEvent<string>) => {
        props.onReceiveMessages([]); // Simplified, normally we send one or more
    }, [props.onReceiveMessages]);

};

字符串
这同样适用于grouppage组件。

const GroupPage = () => {
    const handleNewMessages = useCallback((newMessages: IChatMessage[]) => {
        console.log(messages.length);
        // Code goes here that adds the newMessages to the messages array
    }, [messages]);
};

1bqhqjot

1bqhqjot3#

WebSocketNotificationHandler.tsx:

import { useEffect } from "react";
import config from "../../../config.json";
import { IChatMessage } from "../../../services/chatService";

interface WebSocketNotificationHandlerProps {
  onReceiveMessages: (newMessages: IChatMessage[]) => void;
}

export let client: WebSocket = new WebSocket(
  config.webSocketAddress,
  config.webSocketProtocol
);

const WebSocketNotificationHandler = (
  props: WebSocketNotificationHandlerProps
) => {
  useEffect(() => {
    const handleWebSocketMessage = async (ev: MessageEvent<string>) => {
      props.onReceiveMessages([]); // Simplified, normally we send one or more
    };

    if (client) {
      client.addEventListener("message", handleWebSocketMessage);
    }

    return () => {
      client.removeEventListener("message", handleWebSocketMessage);
    };
  }, []);

  return <></>;
};

export default WebSocketNotificationHandler;

字符串
GroupPage.tsx:

import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import chatService, { IChatMessage, ICreateMessageResponse } from '../../../services/chatService';
import ChatMessage from '../../atoms/ChatMessage/ChatMessage';
import ChatStagingArea from '../../organisms/ChatStagingArea/ChatStagingArea';
import WebSocketNotificationHandler from '../../organisms/WebSocketNotificationHandler/WebSocketNotificationHandler';

const GroupPage = () => {
  const { groupName } = useParams();
  const [messages, setMessages] = useState<IChatMessage[]>([]);

  const loadGroup = async () => {
    setMessages([]);
    await chatService.getGroup(groupName).then(data => {
      setMessages([...data.data.messages]);
    });
  };

  useEffect(() => {
    loadGroup();
  }, []);

  useEffect(() => {
    const handleNewMessages = (newMessages: IChatMessage[]) => {
      setMessages(prevMessages => [...prevMessages, ...newMessages]);
    };

    return () => {
      WebSocketNotificationHandler.client.removeEventListener("message", handleNewMessages);
    };
  }, [messages]);

  const handleMessageSent = (message: ICreateMessageResponse, text: string) => {
    let newMessages = [...messages, { /* Message data for a new message */ }];
    setMessages(newMessages);
  };

  return (
    <>
      {messages && messages.map(m => <ChatMessage key={m.id} author={m.fullName} date={m.authoredDate} message={m.message} />)}
      <ChatStagingArea groupName={groupName} onSent={(message, text) => handleMessageSent(message, text)} />
      <WebSocketNotificationHandler onReceiveMessages={newMessages => handleNewMessages(newMessages)} />
    </>
  );
};

export default GroupPage;


这些更改确保WebSocket事件得到正确处理,并且在新消息到达时正确更新消息状态。清理WebSocket侦听器也是为了防止内存泄漏。

相关问题