将 WebSocket 与 Next.js 结合使用

Cho*_*wka 27 websocket reactjs next.js

我想知道使用 Next.js 页面连接到 WebSockets 服务器的最佳方法是什么?我希望用户可以通过一个连接浏览页面,并且当他关闭页面时,他也会关闭 WebSockets 连接。我尝试使用 React 的 Context 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;
Run Code Online (Sandbox Code Playgroud)

它工作得很好,但在创建连接时却效果不佳。基本new WebSocket语法不起作用,所以我必须使用第三方库,就像react-use-websocket我不喜欢的那样。同样令人不安的是我无法关闭连接。Context 根本不知道页面何时关闭,而且库也没有提供用于关闭连接的钩子。

我想知道在 Next.js 中处理 WebSockets 连接的最佳方法是什么。

Fil*_*ský 66

为了让 ws 在 Next.js 上运行,需要完成很多事情。

首先,重要的是要意识到我希望我的 ws 代码在哪里运行。Next.js 上的 React 代码在两种环境中运行:在服务器上(构建页面或使用 ssr 时)和在客户端上。

在页面构建时建立 ws 连接几乎没有什么用处,这就是为什么我将只介绍客户端 ws。

全局 Websocket 类仅是浏览器的功能,不存在于服务器上。这就是为什么我们需要阻止任何实例化,直到代码在浏览器中运行为止。一种简单的方法是:

export const isBrowser = typeof window !== "undefined";
Run Code Online (Sandbox Code Playgroud)
export const wsInstance = isBrowser ? new WebSocket(...) : null;
Run Code Online (Sandbox Code Playgroud)

此外,您不需要使用反应上下文来保存实例,完全可以将其保留在全局范围内并导入实例,除非您希望延迟打开连接。

如果您仍然决定使用 React 上下文(或在 React 树中的任何位置初始化 ws 客户端),那么记住该实例很重要,这样就不会在每次更新 React 节点时创建它。

const wsInstance = useMemo(() => isBrowser ? new WebSocket(...) : null, []);
Run Code Online (Sandbox Code Playgroud)

或者

const [wsInstance] = useState(() => isBrowser ? new WebSocket(...) : null);
Run Code Online (Sandbox Code Playgroud)

在 React 中创建的任何事件处理程序注册都应该包装在useEffect带有返回函数的内部,该返回函数会删除事件侦听器,这是为了防止内存泄漏,还应该指定依赖项数组。如果您的组件被卸载,该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(...)
 }
}, [])

Run Code Online (Sandbox Code Playgroud)

  • 答案中的 @CrippledTable Websocket 指的是 [web API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket),并且在全球范围内可用,无需导入。 (2认同)
  • 谢谢。我的视野狭隘。我最近尝试在客户端使用 ws npm 包。经过一次精神之旅后,我意识到这是多么天真,现在我明白了这是如何运作的。 (2认同)