WebSocket 实战封装笔记
1. 封装
/utils/ws.ts
1 | import qs from 'qs' |
2 | import store from '@/store' |
3 | |
4 | /** |
5 | * setTimeout 类型 |
6 | */ |
7 | type Timeout = ReturnType<typeof setTimeout> |
8 | /** |
9 | * setInterval 类型 |
10 | */ |
11 | type Interval = ReturnType<typeof setInterval> |
12 | /** |
13 | * 允许null的泛型 |
14 | */ |
15 | type Nullable<T> = T | null |
16 | /** |
17 | * 默认url |
18 | */ |
19 | const baseURL = 'ws://test/api' |
20 | /** |
21 | * 默认重连次数 |
22 | */ |
23 | const reconnectMaxCount = 3 |
24 | /** |
25 | * 默认心跳信息 |
26 | */ |
27 | const message = 'ping' |
28 | /** |
29 | * 默认心跳间隔 |
30 | */ |
31 | const interval = 3000 |
32 | |
33 | /** |
34 | * 默认延时时间 |
35 | */ |
36 | const timeout = 1000 |
37 | |
38 | type AutoReconnect = { |
39 | /** |
40 | *重连尝试次数 默认 3 |
41 | */ |
42 | reconnectMaxCount?: number |
43 | } |
44 | |
45 | type Heartbeat = { |
46 | /** |
47 | * 心跳信息 默认`ping` |
48 | */ |
49 | message: string |
50 | /** |
51 | * 心跳间隔时间 默认 `3000` 毫秒 |
52 | */ |
53 | interval: number |
54 | } |
55 | |
56 | export interface IWSOptions { |
57 | /** |
58 | * 是否自动重连 默认`true` |
59 | */ |
60 | autoReconnect: boolean | AutoReconnect |
61 | /** |
62 | * 心跳 默认 `false` |
63 | */ |
64 | heartbeat: boolean | Heartbeat |
65 | /** |
66 | * url 携带的参数 |
67 | */ |
68 | query: Record<string, string> |
69 | } |
70 | |
71 | class WS { |
72 | url: string |
73 | socket: WebSocket | null = null |
74 | reconnectCount = 0 |
75 | delay: Nullable<Timeout> = null |
76 | timer: Nullable<Interval> = null |
77 | autoReconnect: IWSOptions['autoReconnect'] |
78 | heartbeat: IWSOptions['heartbeat'] |
79 | query: IWSOptions['query'] |
80 | |
81 | constructor(url?: string, options?: IWSOptions) { |
82 | const { |
83 | autoReconnect = true, |
84 | query = {}, |
85 | heartbeat = false |
86 | } = options || {} |
87 | this.autoReconnect = autoReconnect |
88 | this.heartbeat = heartbeat |
89 | this.query = query |
90 | // 处理url |
91 | this.url = |
92 | url || |
93 | baseURL + |
94 | qs.stringify( |
95 | { token: store.getters.token, ...this.query }, |
96 | { addQueryPrefix: true } |
97 | ) |
98 | // 开启连接 |
99 | this.connect() |
100 | } |
101 | |
102 | /** |
103 | * 连接 |
104 | */ |
105 | connect(): void { |
106 | this.close() |
107 | this.socket = new WebSocket(this.url) |
108 | this.onError() |
109 | this.onOpen() |
110 | } |
111 | |
112 | /** |
113 | * 监听连接 |
114 | */ |
115 | onOpen(): void { |
116 | if (this.socket) { |
117 | this.socket.onopen = () => { |
118 | this.send('ping') |
119 | // 开启心跳 |
120 | this.heartbeat && this.startHeartbeat() |
121 | } |
122 | } |
123 | } |
124 | |
125 | /** |
126 | * 开启心跳 |
127 | */ |
128 | startHeartbeat(): void { |
129 | const msg = (this.heartbeat as Heartbeat)?.message || message |
130 | const int = (this.heartbeat as Heartbeat)?.interval || interval |
131 | this.timer = setInterval(() => { |
132 | this.send(msg) |
133 | }, int) |
134 | } |
135 | |
136 | /** |
137 | * 监听错误 |
138 | */ |
139 | onError(): void { |
140 | if (this.socket) { |
141 | this.socket.onerror = () => { |
142 | const count = |
143 | (this.autoReconnect as AutoReconnect)?.reconnectMaxCount || |
144 | reconnectMaxCount |
145 | if (this.autoReconnect && this.reconnectCount < count) { |
146 | this.reconnectCount++ |
147 | this.connect() |
148 | } |
149 | } |
150 | } |
151 | } |
152 | |
153 | /** |
154 | * 关闭连接 |
155 | */ |
156 | close(): void { |
157 | this.socket && this.socket.close() |
158 | this.delay && clearTimeout(this.delay) |
159 | this.timer && clearInterval(this.timer) |
160 | this.socket = null |
161 | } |
162 | |
163 | /** |
164 | * 监听消息 |
165 | * @param callback |
166 | */ |
167 | onMessage(callback: (...data: any[]) => any): void { |
168 | if (this.socket) { |
169 | this.socket.onmessage = data => { |
170 | try { |
171 | const res = JSON.parse(data.data) |
172 | callback(res) |
173 | } catch (err) { |
174 | callback(data) |
175 | } |
176 | } |
177 | } |
178 | } |
179 | |
180 | /** |
181 | * 发送消息 |
182 | * @param data |
183 | */ |
184 | send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void { |
185 | if (!this.socket) return |
186 | // 状态为 `1-开启状态` 直接发送 |
187 | if (this.socket.readyState === this.socket.OPEN) { |
188 | this.socket.send(JSON.stringify(data)) |
189 | // 状态为 `0-开启状态` 则延后调用 |
190 | } else if (this.socket.readyState === this.socket.CONNECTING) { |
191 | this.delay = setTimeout(() => { |
192 | this.socket?.send(data) |
193 | }, timeout) |
194 | // 状态为 `2-关闭中 3-关闭状态` 则重新连接 |
195 | } else { |
196 | this.connect() |
197 | this.delay = setTimeout(() => { |
198 | this.socket?.send(data) |
199 | }, timeout) |
200 | } |
201 | } |
202 | } |
203 | |
204 | export default WS |
2.使用
1 | import WS from '/utils/ws' |
2 | |
3 | const ws = new WS() |
4 | |
5 | ws.onMessage(data => { |
6 | console.log(data) |
7 | }) |
参考链接