将 WebSockets 与 Next.js 结合使用
Using WebSockets with Next.js
我想知道使用 Next.js 个页面连接到我的 WebSockets 服务器的最佳方法是什么?我希望用户可以通过一个连接浏览页面,当他关闭页面时,他也关闭了 WebSockets 连接。我尝试使用 React 的上下文 API:
const WSContext = createContext(null);
const Wrapper = ({ children }) => {
const instance = WebSocket("ws://localhost:3000/ws");
return <WSContext.Provider value={instance}>{children}</WSContext.Provider>;
};
export const useWS = () => useContext(WSContext);
export default Wrapper;
效果很好,但在创建连接时却不行。基本的 new WebSocket
语法不起作用,所以我必须使用第三方库,比如我不喜欢的 react-use-websocket
。同样令人不安的是我无法关闭连接。 Context 根本不知道页面何时关闭,而且库也没有提供关闭连接的钩子。
我想知道在 Next.js 中处理 WebSockets 连接的最佳方法是什么。
为了让 ws 在 Next.js 上工作,需要做很多事情。
首先,重要的是要意识到我希望我的 ws 代码放在哪里 运行。在两种环境中 Next.js 运行s 上响应代码:在服务器上(构建页面时或使用 ssr 时)和客户端上。
在页面构建时建立 ws 连接没有什么用处,这就是为什么我只介绍客户端 ws。
全局 Websocket class 是浏览器独有的功能,服务器上不存在。这就是为什么我们需要在代码在浏览器中 运行 之前阻止任何实例化。一种简单的方法是:
export const isBrowser = typeof window !== "undefined";
export const wsInstance = isBrowser ? new Websocket(...) : null;
此外,您不需要使用 react context 来保存实例,完全可以将其保留在全局范围内并导入实例,除非您希望延迟打开连接。
如果您仍然决定使用 React 上下文(或在 React 树中的任何位置初始化 ws 客户端),memoize 实例很重要,这样它就不会在您的反应节点的每次更新时创建。
const wsInstance = useMemo(() => isBrowser ? new Websocket(...) : null, []);
或
const [wsInstance] = useState(() => isBrowser ? new Websocket(...) : null);
在 React 中创建的任何事件处理程序注册都应包装在 useEffect
中,并带有删除事件侦听器的 return 函数,这是为了防止内存泄漏,同时,依赖数组应该是指定的。如果您的组件已卸载,useEffect
挂钩也会删除事件侦听器。
如果您希望重新初始化 ws 并处理当前连接,则可以执行类似以下操作:
const [wsInstance, setWsInstance] = useState(null);
// Call when updating the ws connection
const updateWs = useCallback((url) => {
if(!browser) return setWsInstance(null);
// Close the old connection
if(wsInstance?.readyState !== 3)
wsInstance.close(...);
// Create a new connection
const newWs = new WebSocket(url);
setWsInstance(newWs);
}, [wsInstance])
// (Optional) Open a connection on mount
useEffect(() => {
if(isBrowser) {
const ws = new WebSocket(...);
setWsInstance(ws);
}
return () => {
// Cleanup on unmount if ws wasn't closed already
if(ws?.readyState !== 3)
ws.close(...)
}
}, [])
我这样配置上下文:
import { createContext, useContext, useMemo, useEffect, ReactNode } from 'react';
type WSProviderProps = { children: ReactNode; url: string };
const WSStateContext = createContext<WebSocket | null>(null);
function WSProvider({ children, url }: WSProviderProps): JSX.Element {
const wsInstance = useMemo(
() => (typeof window != 'undefined' ? new WebSocket(`ws://127.0.0.1:8001/ws${url}`) : null),
[]
);
useEffect(() => {
return () => {
wsInstance?.close();
};
}, []);
return <WSStateContext.Provider value={wsInstance}>{children}</WSStateContext.Provider>;
}
function useWS(): WebSocket {
const context = useContext(WSStateContext);
if (context == undefined) {
throw new Error('useWS must be used within a WSProvider');
}
return context;
}
export { WSProvider, useWS };
我想知道使用 Next.js 个页面连接到我的 WebSockets 服务器的最佳方法是什么?我希望用户可以通过一个连接浏览页面,当他关闭页面时,他也关闭了 WebSockets 连接。我尝试使用 React 的上下文 API:
const WSContext = createContext(null);
const Wrapper = ({ children }) => {
const instance = WebSocket("ws://localhost:3000/ws");
return <WSContext.Provider value={instance}>{children}</WSContext.Provider>;
};
export const useWS = () => useContext(WSContext);
export default Wrapper;
效果很好,但在创建连接时却不行。基本的 new WebSocket
语法不起作用,所以我必须使用第三方库,比如我不喜欢的 react-use-websocket
。同样令人不安的是我无法关闭连接。 Context 根本不知道页面何时关闭,而且库也没有提供关闭连接的钩子。
我想知道在 Next.js 中处理 WebSockets 连接的最佳方法是什么。
为了让 ws 在 Next.js 上工作,需要做很多事情。
首先,重要的是要意识到我希望我的 ws 代码放在哪里 运行。在两种环境中 Next.js 运行s 上响应代码:在服务器上(构建页面时或使用 ssr 时)和客户端上。
在页面构建时建立 ws 连接没有什么用处,这就是为什么我只介绍客户端 ws。
全局 Websocket class 是浏览器独有的功能,服务器上不存在。这就是为什么我们需要在代码在浏览器中 运行 之前阻止任何实例化。一种简单的方法是:
export const isBrowser = typeof window !== "undefined";
export const wsInstance = isBrowser ? new Websocket(...) : null;
此外,您不需要使用 react context 来保存实例,完全可以将其保留在全局范围内并导入实例,除非您希望延迟打开连接。
如果您仍然决定使用 React 上下文(或在 React 树中的任何位置初始化 ws 客户端),memoize 实例很重要,这样它就不会在您的反应节点的每次更新时创建。
const wsInstance = useMemo(() => isBrowser ? new Websocket(...) : null, []);
或
const [wsInstance] = useState(() => isBrowser ? new Websocket(...) : null);
在 React 中创建的任何事件处理程序注册都应包装在 useEffect
中,并带有删除事件侦听器的 return 函数,这是为了防止内存泄漏,同时,依赖数组应该是指定的。如果您的组件已卸载,useEffect
挂钩也会删除事件侦听器。
如果您希望重新初始化 ws 并处理当前连接,则可以执行类似以下操作:
const [wsInstance, setWsInstance] = useState(null);
// Call when updating the ws connection
const updateWs = useCallback((url) => {
if(!browser) return setWsInstance(null);
// Close the old connection
if(wsInstance?.readyState !== 3)
wsInstance.close(...);
// Create a new connection
const newWs = new WebSocket(url);
setWsInstance(newWs);
}, [wsInstance])
// (Optional) Open a connection on mount
useEffect(() => {
if(isBrowser) {
const ws = new WebSocket(...);
setWsInstance(ws);
}
return () => {
// Cleanup on unmount if ws wasn't closed already
if(ws?.readyState !== 3)
ws.close(...)
}
}, [])
我这样配置上下文:
import { createContext, useContext, useMemo, useEffect, ReactNode } from 'react';
type WSProviderProps = { children: ReactNode; url: string };
const WSStateContext = createContext<WebSocket | null>(null);
function WSProvider({ children, url }: WSProviderProps): JSX.Element {
const wsInstance = useMemo(
() => (typeof window != 'undefined' ? new WebSocket(`ws://127.0.0.1:8001/ws${url}`) : null),
[]
);
useEffect(() => {
return () => {
wsInstance?.close();
};
}, []);
return <WSStateContext.Provider value={wsInstance}>{children}</WSStateContext.Provider>;
}
function useWS(): WebSocket {
const context = useContext(WSStateContext);
if (context == undefined) {
throw new Error('useWS must be used within a WSProvider');
}
return context;
}
export { WSProvider, useWS };