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 分享,
如果有更好、更簡潔的方式,也歡迎留言推薦給我囉~
When you subscribe to the blog, we will send you an e-mail when there are new updates on the site so you wouldn't miss them.
評論