多年来我拥有相同的堆栈,直到最近我面对websockets 超过Stomp 客户。
以上解决方案在技术上和精神上都不适合我
原因:
- 我不喜欢带有
Stomp 的通道,因为以更外科手术的方式操作连接的唯一方法是您必须使用全局状态对象(对我来说 - 它是redux)。即使您只存储随机生成的 IDS,这似乎也不正确(使用 unsubscribe 函数它将是......阅读更多 here 关于存储序列化
- 使用容器的方式又是一个痛苦……(你知道在哪里)。再次
redux 和许多底层功能无缘无故地使用
- 再次使用
promises: 的另一种方式,无需通过在生成器中使用promise 来存储有用的连接信息和一些DI。这缩小了实现选择的范围
所以:
- 我需要有连接信息(我决定使用状态但不在:
redux,组件状态。单例状态)。 Stomp 不会强迫你放置 ID,但我这样做是因为我想自己管理连接
- 我需要一个入口点,没有:
promises、iterators 以及许多对未来的我来说会很痛苦的事情。一个“统治所有人”的地方(如我所愿)
- 激活:登录
- 停用:注销
- 订阅:componentDidMount
- 退订:componentWillUnmount
- DI 在一个地方请求(仅在需要时将 store.dispatch 传递给构造函数)
// main topic of the question
我编写了这个非常适合我的实现:
import SockJS from 'sockjs-client';
import {
Client,
IMessage,
messageCallbackType,
StompHeaders,
} from '@stomp/stompjs';
import { Action, Dispatch } from 'redux';
type ConnectionId = string;
interface IServiceConfig {
url: string;
dispatch?: Dispatch;
}
export default class Stomp {
serviceConfig: IServiceConfig = {
dispatch: null,
url: null,
};
ids: ConnectionId[] = [];
stomp: Client;
constructor(config: IServiceConfig) {
this.serviceConfig = { ...config };
this.stomp = new Client();
this.stomp.webSocketFactory = () => {
return (new SockJS(config.url));
};
}
alreadyInQueue = (id: ConnectionId): boolean => {
return Boolean(this.ids.find(_id => id === _id));
};
subscribeByDispatchAction = (
destination: string,
callback: (message: IMessage) => Action,
headers: StompHeaders & {
id: ConnectionId;
},
): void => {
const alreadyInQueue = this.alreadyInQueue(headers.id);
if (!alreadyInQueue) {
this.stomp.subscribe(
destination,
(message) => {
this.serviceConfig.dispatch(callback(message));
},
headers,
);
this.ids.push(headers.id);
return;
}
console.warn(`Already in queue #${headers.id}`);
};
subscribe = (
destination: string,
callback: messageCallbackType,
headers: StompHeaders & {
id: ConnectionId;
},
): void => {
const alreadyInQueue = this.alreadyInQueue(headers.id);
if (!alreadyInQueue) {
this.stomp.subscribe(
destination,
(message) => callback(message),
headers,
);
this.ids.push(headers.id);
this.logState('subscribe');
return;
}
console.warn(`Failed to subscribe over Socks by #${headers.id}`);
};
unsubscribe = (id: ConnectionId, headers?: StompHeaders): void => {
this.stomp.unsubscribe(id, headers);
this.ids.splice(this.ids.indexOf(id), 1);
};
activate = (): void => {
this.stomp.activate();
};
deactivate = (): void => {
if (this.ids.length === 0) {
this.stomp.deactivate();
return;
}
for (let i = 0; i < this.ids.length; i++) {
this.unsubscribe(this.ids[i]);
}
/**
* it seems like it's overkil but
* for me it works only if i do all
* the things as you see below
* - stomp deactivation
* - closing webSockets manually by using native constant // sockjs-client
* - closing webSockets instance by using returned value fron factory
*/
this.stomp.deactivate();
this.stomp.webSocket.close(
this.stomp.webSocket.CLOSED,
);
this.stomp.webSocketFactory().close();
};
getAllIds = (): readonly ConnectionId[] => {
return this.ids;
};
// debug method
logState = (method: string): void => {
/* eslint-disable */
console.group(`Stomp.${method}`);
console.log('this', this);
console.log('this.ids', this.getAllIds());
console.log('this.stomp', this.stomp);
console.groupEnd();
/* eslint-enable */
};
}
我的配置文件
import { store } from '~/index';
import Stomp from '~/modules/_Core/services/Stomp';
import appConfig from '~/modules/Common/services/appConfig';
export const StompService = new Stomp({
dispatch: store?.dispatch,
url: `${appConfig.apiV1}/websocket`,
});
希望对大家有所帮助