如何在 React 中正确的使用 socket.io 客户端?

开发 开发工具
本文的实现方式是使用状态管理工具保存 socket 实例,供子组件使用,如果使用了 React Hooks,可以用其提供的 useContext API,实现起来也很简单。

最近在一个 React 项目中,使用到了 socket.io 处理即时消息,这里面有几点容易被忽视的问题,例如:在 React 单页面应用中如何防止出现多个 socket 实例、在任意的的组件内如何方便的取到 socket 实例、对于某个事件不要随着页面切换出现多个监听器。

在本文中,将会给大家分享下我在 React 中使用 Socket.io 客户端的一些经验,希望对此有疑惑的朋友给予一些帮助,也许你会有一些更好的实现方式,欢迎交流!

创建 Socket Context

本文的实现方式是使用状态管理工具保存 socket 实例,供子组件使用,如果使用了 React Hooks,可以用其提供的 useContext API,实现起来也很简单。

// contexts/socket.tsx
import { createContext, ReactNode, useContext } from 'react';
import io, { Socket } from 'socket.io-client';

const SOCKET_URL = 'ws://localhost:8080';
export const socket = io(SOCKET_URL, {
transports: ['websocket'],
});

const SocketContext = createContext<Socket>(socket);
SocketContext.displayName = 'SocketContext';

export const SocketProvider = ({ children }: { children: ReactNode }) => (
<SocketContext.Provider value={socket}>{children}</SocketContext.Provider>
);

export const useSocket = () => {
const context = useContext(SocketContext);
return context;
};

// contexts/index.tsx
import { ReactNode } from 'react';
import { SocketProvider } from './Socket';

const AppContextProviders = ({ children }: { children: ReactNode }) => (
<SocketProvider>{children}</SocketProvider>
);

export default AppContextProviders;

其中  const socket = io(SOCKET_URL)​,有些朋友可能就有疑问了,为什么不执行下 socket.connect() 呢?

socket.io 客户端默认是自动链接的,如果声明了 autoConnect 属性为 false,则需要手动执行下链接。

以上,在页面第一次加载时会初始化 socket,解决了第一个问题:“React 单页面应用中如何防止出现多个 socket 实例”。

根组件提供 socket

在项目的 App.js 文件中引入我们自定义的 Providers,将 AppProviders 组件做为根组件放在最顶层,这样被包裹的组件都可以使用 AppProviders 组件提供的属性。也解决了第二个问题:“在任意的的组件内如何方便的取到 socket 实例”。

import AppProviders from './contexts';
import './App.css';

const App = () => (
<AppProviders>
...
</AppProviders>
);

export default App;

任意子组件中使用 socket

组件 A,监听服务器发来的消息。

useEffect() 是 React 内置的一个 Hook,如果第二个参数依赖项数组为空,那么传入的第一个函数在该组件内只会执行一次,依赖项数组只要有一个状态被更新,useEffect() 传入的第一个函数也将会被执行。

还需要注意的是 useEffect() 传入的第一个函数,它又返回的函数在函数组件卸载时被调用,通常我们会用 useEffect() 模拟类组件的 componentDidMount、componentWillUnmount 行为。

在组件卸载时,使用 socket.off() 移除事件监听器,实际上这可以预防内存泄漏,同时也解决了最开始提的第三个问题:“对于某个事件不要随着页面切换出现多个监听器”。

import { useEffect } from 'react';
import { useSocket } from '../../contexts/Socket';

const ComponentA = () => {
const socket = useSocket();

useEffect(() => {
// componentDidMount
socket.on('message', handleMessage); // 监听消息
return () => {
// componentWillUnmount
socket.off('message', handleMessage);
};
}, [socket]);

return ();
};

export default ComponentA;

组件 B,发送消息到服务器。

在我们的组件 B 中,也可以使用自定义的 useSocket Hook 获取最开始初始化的 socket 实例,但这并不会产生一个新的 socket 实例。

import { useEffect } from 'react';
import { useSocket } from '../../contexts/Socket';

const ComponentB = () => {
const socket = useSocket();
const handleSendMessage = () => {
socket.emit('compress', data); // 发送消息
}

return <div>
// ...
<button onClick={handleSendMessage}>Send message</button>
</div>;
};

export default ComponentB;

责任编辑:武晓燕 来源: 编程界
相关推荐

2019-07-26 14:40:58

Vue.jsSocket.IO前端

2021-10-25 09:00:37

Node.jsJS前端

2023-11-26 18:31:41

Linux信号

2015-08-05 09:33:21

Javawaitnotify

2015-09-28 11:15:39

UbuntuQGit客户端

2019-10-18 10:43:11

JPASpring Boot Flyway

2017-09-05 15:30:00

JavascriptSocket.ioNode.js

2023-05-13 17:32:51

2010-03-18 16:49:43

Java Socket

2010-12-17 10:16:33

OpenVAS

2009-12-21 10:09:26

WCF创建客户端服务对

2013-04-09 12:18:45

socket.ioC服务器

2011-08-17 10:10:59

2017-02-05 14:20:28

LinuxThunderbird邮件客户端

2011-03-21 14:53:36

Nagios监控Linux

2011-04-06 14:24:20

Nagios监控Linux

2022-09-16 14:13:50

人工智能楼宇自动化

2010-03-18 17:39:46

Java Socket

2010-05-26 09:26:43

Cassandra

2023-05-27 16:27:25

点赞
收藏

51CTO技术栈公众号