選單
GSS 技術部落格
在這個園地裡我們將從技術、專案管理、客戶對談面和大家分享我們多年的經驗,希望大家不管是喜歡或是有意見,都可以回饋給我們,讓我們有機會和大家對話並一起成長!
若有任何問題請來信:gss_crm@gss.com.tw
6 分鐘閱讀時間 (1213 個字)

[JavaScript] WebSocket (ft. React Context)

unsplash-coding104

WebSocket 是網路協定的一種。
Client 端可以透過僅一次的連結,保持與 Server 端的連線,以進行雙向溝通。 

會接觸到 WebSocket 是因為遇到
需要透過 Server 端傳送資料至 Client 端,進行處理的情況。

如: 聊天室中,
Client I 透過 Server 端發送訊息給 Client II,
Client II 就應該進行發送通知、顯示訊息等處理。

WebSocket 雙向溝通的特點正好可以滿足這個需求。

如何使用 WebSocket

JavaScript 使用 WebSocket 的方式,就是建立一個 WebSocket 的實例:

var connection = new WebSocket(webSocketUrl); 


WebSocket 的網址會以 ws 或 wss (SSL 加密) 開頭:

var webSocketUrl = 'ws://host.name.com';

// 或
webSocketUrl = 'wss://host.name.com'; 


建構子回傳的 WebSocket 實例,可以設定的事件 handler 有

  • 連線建立事件 onopen: (this: WebSocket, ev: Event) => any | null
  • 連線關閉事件 onclose: (this: WebSocket, ev: CloseEvent) => any | null
  • 錯誤事件 onerror: (this: WebSocket, ev: Event) => any | null
  • 接收訊息事件 onmessage: (this: WebSocket, ev: MessageEvent) => any | null

設定的方法就是直接賦值就好:

var connection = new WebSocket(webSocketUrl);
connection.onopen = (event: Event) => {
	// do something here
} 

搭配 React Context 使用

雖然直接使用 WebSocket 還算簡單,
但是每次連線都要抓設定檔的 Url、設定基本的事件 handler 還是稍嫌麻煩。

所以如果跟我一樣使用 React 在開發,
非常推薦結合 Context 使用。 (再加上 Hook 更好 XD)

(如果不是使用 React ,後面可以跳過了)

建立 Context

有關 Context 的詳細介紹可以參考之前的文章:

...

React Context - 叡揚部落格

Context Context 是用來處理 App 中指定元件樹的公用資訊。 一般來說,資料是由父元件透過子元件定義的Property傳給子元件。 <ChildComponent data={data}/> 針對某些很多元件都會使用到的資料,使用這種每次用都要傳的傳遞方式很麻煩。 或是定義資料的元件跟實際用到資料的元件差了很多層, 那麼夾在兩者中間的元件就會多了很多次不必要的傳遞。 const App: React.FC = props => { const [fontColo

下面就簡單分享一下我的 WebSocket Context 概念。

首先,
我希望在有需要的頁面使用 useEffect,
在首次 render 之後建立連線,
並在 component unmount 時關閉連線。

所以我的 Context Provider 會提供一個 connect function,
並且他會回傳 WebSocket 連線的關閉 function,
讓我可以很輕鬆的在 useEffect 中做使用。

export const WebsocketContextProvider: React.FC = props => {
    const [ connection, setConnection ] = useState<WebSocket>();
    
    // ...
	
	const connect = () => {
	    var connection = new WebSocket(webSocketUrl);
	    setClient(connection);
	
	    return () => connection.close();
	}

	return (<WebsocketContext.Provider value={{connect}}>
		{props.children}
    </WebsocketContext.Provider>);
}

export const WebsocketExample: React.FC = () => {
    const { connect } = useContext<WebsocketContext>();

	// ...

    useEffect(() => connect(), []);
		
	// ...
} 

再來,
由於接收訊息的行為,
有可能會跟著 State 改變而有所不同,
但單純指派包含 State 的 handler 並不會被同步 State 的改變,
因此我也會希望使用 useEffect 去改變 onmessage

雖然還沒有用過其他的 handler,
不過既然做了onmessage,就乾脆都做一做 XD

BTW
useState 或是 setState 是可以使用 function 做參數的,
所以放事件 handler function 進去要記得多加一層。

export const WebsocketContextProvider: React.FC = props => {
    const [ connection, setConnection ] = useState<WebSocket>();

	// 我希望連接開啟時,
	// 預設行為會印出 url,
	// 所以加一個 url 的參數
    const [ onOpen, setOnOpenState ] = 
		useState<(event: Event, url: string) => void>(() => // 多加一層
            (_: Event, url: string) => defaultOpenEventHandler(url));

    const [ onClose, setOnCloseState ] =
		useState<(event: Event) => void>(() => // 多加一層
			defaultCloseEventHandler);

    const [ onMessage, setOnMessageState ] = 
		useState<(data: any) => void>(() => // 多加一層
			defaultMessageHandler);
    
    const [ onError, setOnErrorState ] = 
    	useState<(event: Event) => void>(() => // 多加一層
    		defaultErrorHandler);
  
	// 多加一層
    const setOnOpen =
		(handler: (event: Event, url: string) => void) => setOnOpenState(() => handler);
    const setOnClose =
		(handler: (event: Event) => void) => setOnCloseState(() => handler);
    const setOnMessage =
		(handler: (data: any) => void) => setOnMessageState(() => handler);
    const setOnError =
		(handler: (event: Event) => void) => setOnErrorState(() => handler);

	// 重設所有 handler
    const resetAllHandlers = () => {
	    setOnOpen((_, url) => defaultOpenEventHandler(url));
	    setOnClose(defaultCloseEventHandler);
	    setOnMessage(defaultMessageHandler);
	    setOnError(defaultErrorHandler);
    }

    const connect = () => {
        var connection = new WebSocket(webSocketUrl);
        setConnection(connection);

        return () => {
          connection.close();
          resetAllHandlers();
        }
    }

	// 避免 handler 初始化時,connection 還沒建立,
	// 所以把 connection 納入考量。
    useEffect(() => {
        if (!!connection) {
            connection.onopen = (event: Event) => onOpen(event, webSocketUrl);
    
            connection.onclose = onClose;
    
    		// 傳過來的 data 是 string,把他轉成 object
            connection.onmessage = (event: MessageEvent) =>
                !!event.data && onMessage(JSON.parse(event.data));
    
            connection.onerror = onError;
        }
    }, [connection, onOpen, onClose, onMessage, onError])

  return (
    <WebsocketContext.Provider value={{
        connect,
        setOnOpen: (handler: (event: Event, url: string) => void) => setOnOpen(handler),
        setOnClose: (handler: (event: Event) => void) => setOnClose(handler),
        setOnMessage: (handler: (data: any) => void) => setOnMessage(handler),
        setOnError: (handler: (event: Event) => void) => setOnError(handler)
    }}>
        {props.children}
    </WebsocketContext.Provider>
  );
}

// 加一下 Hook
export const useWebsocketContext = () => useContext(WebsocketContext);

export const WebsocketExample: React.FC = () => {
    const { connect, setOnMessage } = useWebsocketContext();
    const [ currentValue, setCurrentValue ] = useState<string>();

    useEffect(() => connect(), []);

    useEffect(() => setOnMessage((message: any) => {
      console.log(`${currentValue} => ${message.data}`);
      setCurrentValue(message.data);
    }), [currentValue]);

	// ...
} 

以上就是我的 React WebSocket Context 分享,
如果有更好、更簡潔的方式,也歡迎留言推薦給我囉~

[.Net Core] WebSocket
Java Concurrency #9 - Composing Objects

相關文章

 

評論

尚無評論
已經注冊了? 這裡登入
Guest
2024/05/06, 週一

Captcha 圖像