简体   繁体   English

在 Next.js 中使用 WebSocket

[英]Using WebSockets with Next.js

I'm wondering what's the best approach to connect into my WebSockets server using Next.js' pages?我想知道使用 Next.js 页面连接到我的 WebSockets 服务器的最佳方法是什么? I'd like that the user could navigate through the pages with one connection, and when he closes the page he also closes the WebSockets connection.我希望用户可以通过一个连接浏览页面,当他关闭页面时,他也会关闭 WebSockets 连接。 I tried using React's Context API:我尝试使用 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;

It works great, but it does not when it comes to creating a connection.它工作得很好,但在创建连接时却不然。 The basic new WebSocket syntax is not working, so I must use a third-party library, like react-use-websocket which I dislike.基本的new WebSocket语法不起作用,因此我必须使用第三方库,例如我不喜欢的react-use-websocket What's also disturbing is that I cannot close the connection.同样令人不安的是,我无法关闭连接。 The Context simply does not know when the page is closed, and also the library does not provide a hook for closing connections. Context 根本不知道页面何时关闭,而且库也不提供用于关闭连接的钩子。

I would like to know what's the best approach when it comes to handling WebSockets connection in Next.js.我想知道在 Next.js 中处理 WebSockets 连接的最佳方法是什么。

There are multiple things that need to be done in order for ws to work on Next.js.为了让 ws 在 Next.js 上工作,需要做很多事情。

Firstly, it is important to realize where do I want my ws code to run.首先,重要的是要意识到我希望我的 ws 代码在哪里运行。 React code on Next.js runs in two environments: On the server (when building the page or when using ssr) and on the client. Next.js 上的 React 代码在两种环境中运行:在服务器上(构建页面时或使用 ssr 时)和客户端上。

Making a ws connection at page build time has little utility, thats why I will cover client-side ws only.在页面构建时建立 ws 连接几乎没有用处,这就是为什么我将只介绍客户端 ws。

The global Websocket class is a browser only feature and is not present on the server.全局 Websocket 类是仅浏览器的功能,服务器上不存在。 Thats why we need to prevent any instantiation until the code is run in the browser.这就是为什么我们需要在代码在浏览器中运行之前阻止任何实例化。 One simple way to do so would be:一种简单的方法是:

export const isBrowser = typeof window !== "undefined";
export const wsInstance = isBrowser ? new Websocket(...) : null;

Also, you do not need to use react context for holding the instance, it is perfectly possible to keep it at global scope and import the instance, unless you wish to open the connection lazily.此外,您不需要使用 react 上下文来保存实例,完全可以将其保持在全局范围内并导入实例,除非您希望延迟打开连接。

If you still decide to use react context (or initialize the ws client anywhere in the react tree), it is important to memoize the instance, so that it isn't created on every update of your react node.如果您仍然决定使用 react 上下文(或在 react 树中的任何位置初始化 ws 客户端),记住实例很重要,这样它就不会在每次更新 react 节点时创建。

const wsInstance = useMemo(() => isBrowser ? new Websocket(...) : null, []);

or或者

const [wsInstance] = useState(() => isBrowser ? new Websocket(...) : null);

Any event handler registrations created in react should be wrapped inside a useEffect with a return function which removes the event listener, this is to prevent memory leaks, also, a dependency array should be specified.在反应中创建的任何事件处理程序注册都应该包装在带有返回函数的useEffect ,该函数删除事件侦听器,这是为了防止内存泄漏,此外,还应指定依赖项数组。 If your component is unmounted, the useEffect hook will remove the event listener as well.如果您的组件被卸载, useEffect钩子也将删除事件侦听器。

If you wish to reinitailize the ws and dispose of the current connection, then it is possible to do something similar to the following:如果您希望重新初始化 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(...)
 }
}, [])

I configured context like this:我这样配置上下文:

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 };

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM