在 React 中使用 WebSockets

2025-05-27

在 React 中使用 WebSockets

最近,我不得不在我正在开发的 React 应用程序中使用 WebSocket API,因此在本文中,我将简要解释如何在 React 应用程序中使用它。

什么是 Websocket

根据 MDN 的说法,WebSocket API 是一项先进的技术,它能够在用户浏览器和服务器之间建立双向交互式通信会话。使用此 API,您可以向服务器发送消息并接收事件驱动的响应,而无需轮询服务器以获取回复。WebSocket 可以帮助您在客户端(在我的情况下是 React 应用程序)和服务器之间维护双向通信。

为什么我需要 WebSocket

我曾经参与过一个项目,需要我每 30 秒向服务器发送一次 ping 消息,告知服务器应用程序仍然在线,并跟踪登录到该应用程序的用户及其在线时长。我不想过多地谈论这个细节,但我需要不断地与服务器“通信”,而使用 REST API 来做这件事效率很低。

React 的基本设置

通常,我尝试在组件树中仅使用一个 WebSocket 实例,然后将其传递给需要使用 WebSocket 实例来侦听或向服务器发送消息的其他组件;这是假设您在子组件中侦听特定的 WebSocket 实例。

class Main extends Component {
    ......

    // instance of websocket connection as a class property
    ws = new WebSocket('ws://localhost:3000/ws')

    componentDidMount() {
        this.ws.onopen = () => {
        // on connecting, do nothing but log it to the console
        console.log('connected')
        }

        this.ws.onmessage = evt => {
        // listen to data sent from the websocket server
        const message = JSON.parse(evt.data)
        this.setState({dataFromServer: message})
        console.log(message)
        }

        this.ws.onclose = () => {
        console.log('disconnected')
        // automatically try to reconnect on connection loss

        }

    }

    render(){
        <ChildComponent websocket={this.ws} />
    }
}

Enter fullscreen mode Exit fullscreen mode

在上面的代码片段中,我将其命名为Main组件,因为我认为它应该像父组件一样,为需要使用 WebSocket 实例的子组件提供服务。首先,我们创建一个新的 WebSocket 实例作为类属性ws。然后在componentDidMount方法中,我们可以订阅并监听 WebSocket 提供的一些事件。

  • onopen:当WebSocket连接建立时,会调用onopen事件监听器。
  • onmessage:当从服务器接收到数据时,会发送 onmessage 事件。
  • onclose:当 WebSocket 连接关闭时,会调用 onclose 监听器。

所有这些都在 中设置,componentDidMount因为我们希望这些事件监听器在组件渲染到 DOM 中时可用。此外,我们可以像在 中一样,将 WebSocket 实例作为 props 传递给子组件,<ChildComponent/>这样我们就可以监听该 WebSocket 实例上的任何事件,而不必在每个需要使用该 WebSocket 的组件中都创建新的实例。

但是这种设置有一个问题:一旦出现错误,或者 WebSocket 连接由于某种原因(例如服务器宕机或网络问题等)关闭,连接将无法重新建立,直到componentDidMount再次调用该函数(例如通过刷新页面)。我认为这不是我们想要的。

高级设置

此设置改编自两个 StackOverflow 答案,如何在关闭连接后重新连接到 WebSocketWebSocket:如何在连接终止后自动重新连接

class Main extends Component {
    constructor(props) {
        super(props);

        this.state = {
            ws: null
        };
    }

    // single websocket instance for the own application and constantly trying to reconnect.

    componentDidMount() {
        this.connect();
    }

    timeout = 250; // Initial timeout duration as a class variable

    /**
     * @function connect
     * This function establishes the connect with the websocket and also ensures constant reconnection if connection closes
     */
    connect = () => {
        var ws = new WebSocket("ws://localhost:3000/ws");
        let that = this; // cache the this
        var connectInterval;

        // websocket onopen event listener
        ws.onopen = () => {
            console.log("connected websocket main component");

            this.setState({ ws: ws });

            that.timeout = 250; // reset timer to 250 on open of websocket connection 
            clearTimeout(connectInterval); // clear Interval on on open of websocket connection
        };

        // websocket onclose event listener
        ws.onclose = e => {
            console.log(
                `Socket is closed. Reconnect will be attempted in ${Math.min(
                    10000 / 1000,
                    (that.timeout + that.timeout) / 1000
                )} second.`,
                e.reason
            );

            that.timeout = that.timeout + that.timeout; //increment retry interval
            connectInterval = setTimeout(this.check, Math.min(10000, that.timeout)); //call check function after timeout
        };

        // websocket onerror event listener
        ws.onerror = err => {
            console.error(
                "Socket encountered error: ",
                err.message,
                "Closing socket"
            );

            ws.close();
        };
    };

    /**
     * utilited by the @function connect to check if the connection is close, if so attempts to reconnect
     */
    check = () => {
        const { ws } = this.state;
        if (!ws || ws.readyState == WebSocket.CLOSED) this.connect(); //check if websocket instance is closed, if so call `connect` function.
    };

    render() {
        return <ChildComponent websocket={this.state.ws} />;
    }
}
Enter fullscreen mode Exit fullscreen mode

上述高级设置只是确保如果服务器宕机或出现网络故障,WebSocket 总是尝试连接,因此只要服务器恢复,客户端就会重新连接。

我将逐步讲解此设置的作用,connect调用 方法来启动 WebSocket 连接componentDidMount。声明一个名为 的类属性timeout并将其设置为 250ms,然后我们有两个函数connectcheck我将详细介绍这些函数的作用。

  • check- 此函数用于检查是否没有 WebSocket 实例或 WebSocket 连接已关闭,如果是,则connect调用该函数。

  • connect- 此函数主要管理 WebSocket 连接,我们在这里监听onopenoncloseonerror事件。在onopen监听器中,WebSocket 实例被添加到状态中,以便可以将其作为 props 传递给想要监听它的子组件。然后,设置 timeout 变量250ms并清除 setInterval 。

在监听器中,onclose超时值会增加,并check在 setTimeout 中以增加的超时值调用该函数。一旦超时值超过 10000 毫秒(10 秒),它就会停止增加。我这样做是为了防止服务器主动尝试重新连接,而是在尝试重新连接之前延迟一段时间。

有一些库可以帮你实现这一点,比如ReconnectingWebSocket,但这个库和我的设置都没有实现指数退避算法,而该算法可以在大量客户端尝试重新连接服务器时帮助管理服务器流量泛滥。一个名为@gamestdio/websocket的库实现了指数退避算法,所以如果你要处理大量客户端应用程序,可以使用它。

使用 WebSocket 发送消息

这种高级设置存在一个问题:当 WebSocket 连接关闭时,oncloseWebSocket 实例的监听器可能会被设置null为一段时间,这样做是为了确保在连接关闭并再次打开时不会打开新的 WebSocket 实例。这里的问题是,如果在 WebSocket 实例为空时尝试向服务器发送数据,可能会破坏应用程序。那么,我们该如何解决这个问题呢?答案是在组件中需要发送数据的任何位置使用 try catch 块。



class ChildComponent extends Component {

    sendMessage=()=>{
        const {websocket} = this.props // websocket instance passed as props to the child component.

        try {
            websocket.send(data) //send data to the server
        } catch (error) {
            console.log(error) // catch error
        }
    }
    render() {
        return (
            <div>
                ........
            </div>
        );
    }
}

export default ChildComponent;
Enter fullscreen mode Exit fullscreen mode

结论

我希望本教程能帮助你在 React 应用程序中设置 WebSocket,因为这正是我写这篇文章的动机。我刚开始在客户端使用 WebSocket,所以如果你觉得有什么可以改进的地方,请留言。

文章来源:https://dev.to/finallynero/using-websockets-in-react-4fkp
PREV
开发商是什么:一家不盈利的公司为何能有70%的利润率
NEXT
Formik Material UI React Form 使用 Formik、Material-UI 和 Yup。