【问题标题】:WebSockets with functional components带有功能组件的 WebSockets
【发布时间】:2020-02-14 08:13:15
【问题描述】:

我在我的 React 应用程序中使用 websockets。应用程序应该只包含功能组件。

想象一下应用程序包含两个“选项卡”:一个不相关,另一个是使用 websockets 的聊天。鉴于此,我们希望在用户进入聊天选项卡后建立一个 websocket 连接。

组件应该如何处理 websocket 对象?由于我们想在用户切换回另一个选项卡 (WebSocket.close()) 后进行清理,因此听起来我们应该在这里使用效果挂钩。

const Chat = () => {
    const [messages, setMessages] = useState([]);
    useEffect(() => {
        const webSocket = new WebSocket("ws://url");
        webSocket.onmessage = (message) => {
            setMessages(prev => [...prev, message.data]);
        };
        return () => webSocket.close();
    }, []);
    return <p>{messages.join(" ")}</p>;
};

有效!但是现在,假设我们想在useEffect 范围之外的某个地方引用webSocket 变量——比如说,一旦用户点击了一个按钮,我们就想向服务器发送一些东西。

现在,应该如何实施?我目前的想法(尽管我觉得这是一个有缺陷的想法)是:

const Chat = () => {
    const [messages, setMessages] = useState([]);
    const [webSocket] = useState(new WebSocket("ws://url"));
    useEffect(() => {
        webSocket.onmessage = (message) => {
            setMessages(prev => [...prev, message.data]);
        };
        return () => webSocket.close();
    }, []);
    return <p>{messages.join(" ")}</p>;
};

有点效果,不过我觉得有更好的解决方案。

【问题讨论】:

  • 您的问题应该在codereview.stackexchange.com
  • useRef 进行救援。此外,现在您正在订阅每次渲染的新消息。我相信只做一次就足够了(useEffect( ... , [])useEffect( ..., [webSocketRef.current] ),以防在某个时刻重新创建套接字是有意义的)

标签: javascript reactjs react-hooks


【解决方案1】:

正如@skyboyer 在您的问题下的评论中所写,您可以使用useRef 挂钩来保存WebSocket,因为您不会更改或重新创建WebSocket 对象,所以它会更正确。所以你不需要useState钩子。

useRef() Hook 不仅仅适用于 DOM 引用。 “ref”对象是一个通用容器,其当前属性是可变的并且可以保存任何值,类似于类上的实例属性。 more

因此您可以将代码更改为:

const Chat = () => {
    const [messages, setMessages] = useState([]);
    const webSocket = useRef(null);

    useEffect(() => {
        webSocket.current = new WebSocket("ws://url");
        webSocket.current.onmessage = (message) => {
            setMessages(prev => [...prev, message.data]);
        };
        return () => webSocket.current.close();
    }, []);
    return <p>{messages.join(" ")}</p>;
};

【讨论】:

  • 这是我寻求的解决方案,但是:useRef(new WebSocket("ws://url")); 在每次重新渲染时构造另一个 WebSocket 实例。传递给 useRef 的东西在每次重新渲染时都会被评估,只是如果不是初始渲染,它们就会被丢弃。我的解决方案是只useRef(null),然后在效果中构造它
  • @JohnSmith 是的,你是对的。我已经更新了我的答案
  • 你真的可以用这样的回调调用setMessages吗?这将非常有用,但官方文档中对此没有任何内容。
  • 没关系!我找到了。参考:reactjs.org/docs/hooks-reference.html#functional-updates
【解决方案2】:

可能一个不错的选择是在一个单独的 WS 实例中管理 WS 操作并在需要的地方使用它。

class WebSocketClient {
    static instance = null;
    callbacks = {};

    static getInstance() {
        if (!WebSocketClient.instance) WebSocketClient.instance = new WebSocketClient();
        return WebSocketClient.instance;
    }

    constructor() {
        this.socketRef = null;
    }

    addCallbacks = (...callbacks) => this.callbacks = { ...callbacks };

    connect = () => {
        const path = 'YOUR_SOCKET_PATH';
        this.socketRef = new WebSocket(path);
        this.socketRef.onopen = () => {
            console.log('WebSocket open');
        };

        this.socketRef.onmessage = e => {
            this.socketNewMessage(e.data);
        };

        this.socketRef.onerror = e => {
            console.log(e.message);
        };

        this.socketRef.onclose = () => {
            console.log("WebSocket closed let's reopen");
            this.connect();
        };
    }

    state = () => this.socketRef.readyState;

    waitForSocketConnection = (callback) => {
        const socket = this.socketRef;
        const recursion = this.waitForSocketConnection;
        setTimeout(
            () => {
                if (socket.readyState === 1) {
                    console.log("Connection is made")
                    if (callback != null) {
                        callback();
                    }
                    return;
                } else {
                    console.log("wait for connection...")
                    recursion(callback);
                }
            },
        1);
    }

}

export default WebSocketClient.getInstance();

所以在你的组件中将会是。

import React, { useEffect } from 'react';
import WSC from 'wsc';


const Test = ({}) => {

    useEffect(()=>{
        WSC.connect();
        WSC.waitForSocketConnection(()=>{
            'HERE_YOUR_CALLBACKS'
        });
    },[])
}

这种方式总是返回相同的实例。

【讨论】:

    猜你喜欢
    • 2020-08-14
    • 2020-12-30
    • 2020-06-25
    • 1970-01-01
    • 1970-01-01
    • 2021-02-18
    • 2020-08-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多