簡體   English   中英

在 Next.js 中使用 WebSocket

[英]Using WebSockets with 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;

它工作得很好,但在創建連接時卻不然。 基本的new WebSocket語法不起作用,因此我必須使用第三方庫,例如我不喜歡的react-use-websocket 同樣令人不安的是,我無法關閉連接。 Context 根本不知道頁面何時關閉,而且庫也不提供用於關閉連接的鈎子。

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

為了讓 ws 在 Next.js 上工作,需要做很多事情。

首先,重要的是要意識到我希望我的 ws 代碼在哪里運行。 Next.js 上的 React 代碼在兩種環境中運行:在服務器上(構建頁面時或使用 ssr 時)和客戶端上。

在頁面構建時建立 ws 連接幾乎沒有用處,這就是為什么我將只介紹客戶端 ws。

全局 Websocket 類是僅瀏覽器的功能,服務器上不存在。 這就是為什么我們需要在代碼在瀏覽器中運行之前阻止任何實例化。 一種簡單的方法是:

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

此外,您不需要使用 react 上下文來保存實例,完全可以將其保持在全局范圍內並導入實例,除非您希望延遲打開連接。

如果您仍然決定使用 react 上下文(或在 react 樹中的任何位置初始化 ws 客戶端),記住實例很重要,這樣它就不會在每次更新 react 節點時創建。

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

或者

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

在反應中創建的任何事件處理程序注冊都應該包裝在帶有返回函數的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(...)
 }
}, [])

我這樣配置上下文:

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