WebSocket 是網路協定的一種。
Client 端可以透過僅一次的連結,保持與 Server 端的連線,以進行雙向溝通。
會接觸到 WebSocket 是因為遇到
需要透過 Server 端傳送資料至 Client 端,進行處理的情況。
如: 聊天室中,
Client I 透過 Server 端發送訊息給 Client II,
Client II 就應該進行發送通知、顯示訊息等處理。
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 有
設定的方法就是直接賦值就好:
var connection = new WebSocket(webSocketUrl); connection.onopen = (event: Event) => { // do something here }
雖然直接使用 WebSocket 還算簡單,
但是每次連線都要抓設定檔的 Url、設定基本的事件 handler 還是稍嫌麻煩。
所以如果跟我一樣使用 React 在開發,
非常推薦結合 Context 使用。 (再加上 Hook 更好 XD)
(如果不是使用 React ,後面可以跳過了)
有關 Context 的詳細介紹可以參考之前的文章:
下面就簡單分享一下我的 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 分享,
如果有更好、更簡潔的方式,也歡迎留言推薦給我囉~