React(TypeScript)中WebSocket控制流困惑及延迟连接诉求
关于React(TypeScript)中WebSocket的两个问题解答
一、为什么先创建WebSocket再绑定事件不会出现竞态条件?
原生WebSocket的连接建立是异步触发的宏任务,而你创建对象后绑定onopen/onmessage的代码属于同步执行逻辑。浏览器的事件循环规则是:先执行完当前同步任务队列里的所有代码,再去处理异步宏任务(比如WebSocket连接成功的事件回调)。
也就是说,哪怕WebSocket连接建立得再快,onopen事件的触发时机也一定在你绑定完回调函数之后,绝不会出现“连接成功但回调还没绑定”的情况,你的测试代码能正常运行就是这个原因。
二、如何实现延迟连接的WebSocket(配合Jotai集中管理)
原生WebSocket构造函数确实一调用就会发起连接,没法直接实现“先创建对象再手动触发连接”,但可以通过封装类来实现这个逻辑:
封装延迟连接的WebSocket类
class DeferredWebSocket { private ws: WebSocket | null = null; private url: string; private onOpenCallback?: (event: Event) => void; private onMessageCallback?: (event: MessageEvent) => void; constructor(url: string) { this.url = url; } // 设置onopen回调 onOpen(callback: (event: Event) => void) { this.onOpenCallback = callback; // 如果已经连接完成,直接触发回调 if (this.ws?.readyState === WebSocket.OPEN) { callback(new Event('open')); } } // 设置onmessage回调 onMessage(callback: (event: MessageEvent) => void) { this.onMessageCallback = callback; if (this.ws) { this.ws.onmessage = callback; } } // 手动触发连接 connect() { // 避免重复发起连接 if (this.ws?.readyState === WebSocket.CONNECTING || this.ws?.readyState === WebSocket.OPEN) { return; } this.ws = new WebSocket(this.url); this.ws.onopen = (event) => { this.onOpenCallback?.(event); }; this.ws.onmessage = (event) => { this.onMessageCallback?.(event); }; } // 发送消息 send(data: string) { if (this.ws?.readyState === WebSocket.OPEN) { this.ws.send(data); } } // 关闭连接 close(code?: number, reason?: string) { this.ws?.close(code, reason); this.ws = null; } }
配合Jotai集中管理
在Jotai中创建atom存储封装后的实例,按需调用connect触发连接:
import { atom, useAtomValue } from 'jotai'; import constants from './constants'; // 创建DeferredWebSocket实例的atom export const wsAtom = atom(new DeferredWebSocket(constants.WS_URI)); // 组件中使用示例 function ChatComponent() { const ws = useAtomValue(wsAtom); const user = useAtomValue(userAtom); // 假设存在存储用户信息的atom useEffect(() => { // 绑定回调 ws.onOpen(() => { ws.send(JSON.stringify({ command: "login", token: "Bearer " + user.token, })); }); ws.onMessage((event) => { const data = JSON.parse(event.data); console.log(`**${data.username}**: ${data.message}\n`); }); // 组件挂载时触发连接(也可绑定到按钮点击等交互事件) ws.connect(); // 组件卸载时关闭连接 return () => { ws.close(); }; }, [ws, user]); return <div>聊天组件</div>; }
这样就能实现“先创建实例,之后手动触发连接”的需求,同时通过Jotai集中管理WebSocket实例。
内容的提问来源于stack exchange,提问作者Teekin
相关产品推荐
相关产品推荐

