如何在 React 应用中正确使用 socket.io-client
背景
以下是我第一篇获得 8000+ 阅读量和 Google SERP 排名第一的博客。然而,它有一些问题,所以我决定重写它。
在本文中,我使用全局socket
变量来管理 React 应用中的套接字事件。如下所示:
// service/socket.js
export const socket = socketio.connect(SOCKET_URL);
// elsewhere
import {socket} from "service/socket";
然而,其他开发者在评论中建议使用 React context API。我开始觉得使用全局变量也不符合 React 的做法。(虽然我相信那样也行得通,因为套接字的状态不会改变。)
我更新了之前的文章,但觉得需要更多解释。所以我决定写一篇文章,展示如何使用 React context API 来管理一个全局 Socket 实例。
1. 创建套接字上下文
我们将使用useContext hook 为整个应用程序提供 SocketContext。
在以下位置创建文件context/socket.js
:
import socketio from "socket.io-client";
import { SOCKET_URL } from "config";
export const socket = socketio.connect(SOCKET_URL);
export const SocketContext = React.createContext();
2. 使用套接字上下文并提供一个值
在项目的根目录或使用套接字的最大范围内添加 SocketContext 提供程序:
import {SocketContext, socket} from 'context/socket';
import Child from 'components/Child';
const App = () => {
return (
<SocketContext.Provider value={socket}>
<Child />
<Child />
...
</SocketContext.Provider
);
};
3. 现在你可以在任何子组件中使用套接字
例如,在GrandChild
组件中,你可以像这样使用套接字:
import React, {useState, useContext, useCallback, useEffect} from 'react';
import {SocketContext} from 'context/socket';
const GrandChild = ({userId}) => {
const socket = useContext(SocketContext);
const [joined, setJoined] = useState(false);
const handleInviteAccepted = useCallback(() => {
setJoined(true);
}, []);
const handleJoinChat = useCallback(() => {
socket.emit("SEND_JOIN_REQUEST");
}, []);
useEffect(() => {
// as soon as the component is mounted, do the following tasks:
// emit USER_ONLINE event
socket.emit("USER_ONLINE", userId);
// subscribe to socket events
socket.on("JOIN_REQUEST_ACCEPTED", handleInviteAccepted);
return () => {
// before the component is destroyed
// unbind all event handlers used in this component
socket.off("JOIN_REQUEST_ACCEPTED", handleInviteAccepted);
};
}, [socket, userId, handleInviteAccepted]);
return (
<div>
{ joined ? (
<p>Click the button to send a request to join chat!</p>
) : (
<p>Congratulations! You are accepted to join chat!</p>
) }
<button onClick={handleJoinChat}>
Join Chat
</button>
</div>
);
};
好的,这里有一些解释:
什么是useContext
?
useContext
提供一种使用全局状态的 React 方法- 您可以在任何子组件中使用上下文
- 上下文值是状态。React 会注意到它们的变化并触发重新渲染。
这是啥useCallback
?为什么把所有处理程序都放进去useCallback
?
useCallback
防止在状态更新时重新分配- 仅当第二个参数中的元素更新时,函数才会重新分配
- 由于我们将一个空数组传递给第二个参数,因此函数仅被分配一次
- 你可能会忘记(或者懒得)使用
useCallback
。但是,如果你的项目中有很多状态和组件,你可能会面临严重的性能问题
作为第二个参数提供的数组useEffect
是什么?[socket]
-
第二个参数称为依赖项数组。React 将监视依赖项数组元素,每当其中一个元素更新时,就会执行第一个参数函数。
-
如果省略依赖数组
useEffect
,则只要有状态更新,就会执行该函数。 -
如果依赖数组为空数组,则该函数将仅执行一次。
-
在 React 函数组件中,你可以按照以下方式编写
componentDidMount
和替代:componentWillUnmount
useEffect(() => {
// here is componentDidMount
return () => {
// here is componentWillUnmount
}
}, []);
- 强烈建议将第一个参数函数中使用的每个状态放入依赖数组中。
奖金
如果您想使用 JWT 令牌来验证套接字连接,您可以执行以下操作:
const getSocket = () => {
const token = getAuthToken(); // get jwt token from local storage or cookie
if (token) {
return socketio.connect(SOCKET_URL, {
query: { token }
});
}
return socketio.connect(SOCKET_URL);
};
然后在套接字服务器中,您可以像下面这样获取jwt令牌:
import SocketIO from "socket.io";
const io = new SocketIO.Server(expressApp);
const jwtMiddleware = (socket, next) => {
const {token} = socket.handshake.query;
// verify token
};
io.use(jwtMiddleware);