前端抽象化,打破框架枷锁:web的多端通讯抽象
多端通讯
开发前端应用,与其他平台的通讯是常见需求,比如以下场景:
- iframe嵌套网页
- webview和APP或者小程序通讯
- websocket通讯
- 开发桌面端应用的IPC通讯
- webworker通讯
这些通讯机制的核心在于消息的发送与接收,通常遵循以下模式:
// 发送消息
port1.postMessage('message', data)
// 接收消息
port2.onMessage = (data) => {
console.log(data)
}
尽管不同通讯方式的 API 和配置参数各异,其本质均为点对点消息传递。因此,抽象出一个通用的多端通讯模块,不仅能统一接口,还能提升开发效率和代码可维护性。
吸取之前《前端抽象化,打破框架枷锁》系列的经验,本文提出一种更精简、更实用的设计方案
核心内容
核心模块
- 桥对象(Bridge):桥对象是一个抽象接口,封装了多端通讯的核心功能,包括:
- 消息发送与接收
- 两个生命周期
- 监听器注册与删除
该接口需根据具体场景(如 iframe、WebSocket 等)实现不同的桥实例,以适配多样化的通讯需求。
- 桥监听器(BridgeListener):桥监听器以统一格式定义事件监听,包含:
- 事件名称
- 回调函数
其设计保持极简,仅提供基础功能,开发者可根据需求扩展高级特性,如优先级或过滤条件。
- 桥数据对象(BridgeDTO):用统一格式来定义消息的数据,这部分基本上是不用改动的,除非你特殊需要
- 桥工厂(BridgeFactory):桥工厂采用工厂模式,负责根据通讯场景动态创建桥对象。相比适配器模式,工厂模式提供了更高的灵活性,便于开发者定制特定场景的桥实现。某些功能则只有工厂模式情况下,可以高质量的维护,比如:
- 桥的生命周期管理
- 桥的配置的预处理:比如iframe的targetOrigin
- 依赖注入和组合
宏观的整体依赖关系
TS的抽象源码
/**
* 桥接类型枚举
*/
export enum BridgeEnum {
/** WebSocket通信 */
WEBSOCKET = 'websocket',
/** iframe通信 */
IFRAME = 'iframe',
/** WebView通信 */
WEBVIEW = 'webview'
}
/**
* 桥接数据传输对象
* @template T - 请求数据类型
* @template O - 响应数据类型
*/
export interface BridgeDTO<T = any, O = any> {
/** 桥接类型 */
bridgeType: BridgeEnum;
/** 请求数据 */
payload?: T;
}
/**
* 桥接状态枚举
*/
export enum BridgeStatus {
DISCONNECTED = 'disconnected',
CONNECTED = 'connected'
}
import { BridgeDTO, BridgeEnum, BridgeStatus } from "./entities";
/**
* 桥接监听器接口,用于处理接收到的消息
*/
export interface BridgeListener {
/** 监听器类型 */
listentType: BridgeEnum;
/** 消息处理回调函数 */
onListent: (data: BridgeDTO) => void;
}
/**
* 桥接接口,定义通信的基本操作
*/
export interface Bridge {
/** 通信目标对象 */
target: any;
/** 监听器集合 */
listeners: Record<string, BridgeListener[]>;
/** 连接状态 */
status: BridgeStatus;
/** 连接钩子 */
connectHooks:Set<Function>
/** 断开连接钩子 */
disconnectHooks:Set<Function>
/**
* 建立连接
* @returns {void}
*/
connect(): void;
/**
* 添加连接钩子
* @param {Function} hook - 钩子函数
* @returns {void}
*/
addConnectHook(hook: Function): void;
/**
* 添加断开连接钩子
* @param {Function} hook - 钩子函数
* @returns {void}
*/
addDisconnectHook(hook: Function): void;
/**
* 断开连接
* @returns {void}
*/
disconnect(): void;
/**
* 发送数据
* @template T - 请求数据类型
* @template O - 响应数据类型
* @param {BridgeDTO<T, O>} data - 要发送的数据
* @returns {void}
*/
send<T, O>(data: BridgeDTO<T, O>): void;
/**
* 添加消息监听器
* @param {BridgeListener} listener - 监听器对象
* @returns {void}
*/
addListener(listener: BridgeListener): void;
/**
* 移除消息监听器
* @param {BridgeListener} listener - 要移除的监听器对象
* @returns {void}
*/
removeListener(listener: BridgeListener): void;
}
import { Bridge } from './Bridge';
import { WebSocketBridge } from './WebSocketBridge';
import { IframeBridge } from './IframeBridge';
import { WebViewBridge } from './WebViewBridge';
import { BridgeEnum } from './entities';
/**
* 桥接工厂类,用于创建和管理不同类型的桥接实例
*/
export class BridgeFactory {
/** 桥接实例缓存 */
private static bridges: Map<BridgeEnum, Bridge> = new Map();
/**
* 创建桥接实例
* @param {BridgeEnum} type - 桥接类型
* @param {Record<string,any>} options - 创建选项
* @returns {Bridge} 桥接实例
* @throws {Error} 不支持的桥接类型
*/
public static createBridge(type: BridgeEnum, options: Record<string,any>): Bridge {
let bridge = this.bridges.get(type);
if (!bridge) {
switch (type) {
case BridgeEnum.WEBSOCKET:
bridge = new WebSocketBridge(options.url);
break;
case BridgeEnum.IFRAME:
bridge = new IframeBridge(options.target, options.targetOrigin);
break;
case BridgeEnum.WEBVIEW:
bridge = new WebViewBridge(options.nativeBridge);
break;
default:
throw new Error(`Unsupported bridge type: ${type}`);
}
this.bridges.set(type, bridge);
}
return bridge;
}
/**
* 获取桥接实例
* @param {BridgeEnum} type - 桥接类型
* @returns {Bridge | undefined} 桥接实例
*/
public static getBridge(type: BridgeEnum): Bridge | undefined {
return this.bridges.get(type);
}
/**
* 销毁桥接实例
* @param {BridgeEnum} type - 桥接类型
* @returns {void}
*/
public static destroyBridge(type: BridgeEnum): void {
const bridge = this.bridges.get(type);
if (bridge) {
bridge.disconnect();
this.bridges.delete(type);
}
}
}
import { Bridge, BridgeListener } from './Bridge';
import { BridgeDTO, BridgeEnum, BridgeStatus } from './entities';
/**
* iframe桥接实现类,用于通过iframe进行跨窗口通信
* @implements {Bridge}
*/
export class IframeBridge implements Bridge {
/** iframe窗口对象 */
target: Window;
/** 监听器集合,按桥接类型分组 */
listeners: Record<string, BridgeListener[]>;
/** 当前连接状态 */
status: BridgeStatus = BridgeStatus.DISCONNECTED;
/** 连接钩子集合 */
connectHooks: Set<Function> = new Set();
/** 断开连接钩子集合 */
disconnectHooks: Set<Function> = new Set();
/** 目标窗口的源 */
private targetOrigin: string;
/**
* 创建iframe桥接实例
* @param {Window} target - iframe窗口对象
* @param {string} targetOrigin - 目标窗口的源
*/
constructor(target: Window, targetOrigin: string) {
this.target = target;
this.targetOrigin = targetOrigin;
this.listeners = {};
this.setupMessageListener();
}
/**
* 建立iframe连接
* @returns {void}
*/
connect(): void {
this.status = BridgeStatus.CONNECTED;
this.connectHooks.forEach(hook => hook());
}
/**
* 断开iframe连接
* @returns {void}
*/
disconnect(): void {
window.removeEventListener('message', this.handleMessage);
this.status = BridgeStatus.DISCONNECTED;
this.disconnectHooks.forEach(hook => hook());
}
/**
* 发送数据到iframe窗口
* @template T - 请求数据类型
* @template O - 响应数据类型
* @param {BridgeDTO<T, O>} data - 要发送的数据
* @returns {void}
*/
send<T, O>(data: BridgeDTO<T, O>): void {
this.target.postMessage(JSON.stringify(data), this.targetOrigin);
}
/**
* 添加连接钩子
* @param {Function} hook - 钩子函数
* @returns {void}
*/
addConnectHook(hook: Function): void {
this.connectHooks.add(hook);
}
/**
* 添加断开连接钩子
* @param {Function} hook - 钩子函数
* @returns {void}
*/
addDisconnectHook(hook: Function): void {
this.disconnectHooks.add(hook);
}
/**
* 添加消息监听器
* @param {BridgeListener} listener - 监听器对象
* @returns {void}
*/
addListener(listener: BridgeListener): void {
const type = listener.listentType;
if (!this.listeners[type]) {
this.listeners[type] = [];
}
this.listeners[type].push(listener);
}
/**
* 移除消息监听器
* @param {BridgeListener} listener - 要移除的监听器对象
* @returns {void}
*/
removeListener(listener: BridgeListener): void {
const type = listener.listentType;
const listeners = this.listeners[type];
if (listeners) {
const index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
}
}
/**
* 设置消息监听器
* @private
* @returns {void}
*/
private setupMessageListener(): void {
window.addEventListener('message', this.handleMessage.bind(this));
}
/**
* 处理接收到的消息
* @private
* @param {MessageEvent} event - 消息事件对象
* @returns {void}
*/
private handleMessage(event: MessageEvent): void {
if (event.origin !== this.targetOrigin) {
return;
}
try {
const data = JSON.parse(event.data) as BridgeDTO;
this.notifyListeners(data);
} catch (error) {
console.error('Failed to parse message:', error);
}
}
/**
* 通知所有监听器接收到的消息
* @private
* @param {BridgeDTO} data - 接收到的数据
* @returns {void}
*/
private notifyListeners(data: BridgeDTO): void {
const typeListeners = this.listeners[data.bridgeType];
if (typeListeners) {
typeListeners.forEach(listener => {
listener.onListent(data);
});
}
}
}
import { Bridge, BridgeListener } from './Bridge';
import { BridgeDTO, BridgeEnum, BridgeStatus } from './entities';
/**
* WebView桥接实现类,用于通过原生WebView进行通信
* @implements {Bridge}
*/
export class WebViewBridge implements Bridge {
/** WebView目标对象 */
target: any;
/** 监听器集合,按桥接类型分组 */
listeners: Record<string, BridgeListener[]>;
/** 当前连接状态 */
status: BridgeStatus = BridgeStatus.DISCONNECTED;
/** 连接钩子集合 */
connectHooks: Set<Function> = new Set();
/** 断开连接钩子集合 */
disconnectHooks: Set<Function> = new Set();
/** 原生桥接对象 */
private nativeBridge: any;
/**
* 创建WebView桥接实例
* @param {any} nativeBridge - 原生桥接对象
*/
constructor(nativeBridge: any) {
this.nativeBridge = nativeBridge;
this.listeners = {};
this.setupNativeBridge();
}
/**
* 建立WebView连接
* @returns {void}
*/
connect(): void {
this.status = BridgeStatus.CONNECTED;
this.connectHooks.forEach(hook => hook());
}
/**
* 断开WebView连接
* @returns {void}
*/
disconnect(): void {
this.nativeBridge = null;
this.status = BridgeStatus.DISCONNECTED;
this.disconnectHooks.forEach(hook => hook());
}
/**
* 发送数据到原生WebView
* @template T - 请求数据类型
* @template O - 响应数据类型
* @param {BridgeDTO<T, O>} data - 要发送的数据
* @returns {void}
*/
send<T, O>(data: BridgeDTO<T, O>): void {
if (this.nativeBridge) {
this.nativeBridge.postMessage(JSON.stringify(data));
} else {
console.error('Native bridge is not available');
}
}
/**
* 添加连接钩子
* @param {Function} hook - 钩子函数
* @returns {void}
*/
addConnectHook(hook: Function): void {
this.connectHooks.add(hook);
}
/**
* 添加断开连接钩子
* @param {Function} hook - 钩子函数
* @returns {void}
*/
addDisconnectHook(hook: Function): void {
this.disconnectHooks.add(hook);
}
/**
* 添加消息监听器
* @param {BridgeListener} listener - 监听器对象
* @returns {void}
*/
addListener(listener: BridgeListener): void {
const type = listener.listentType;
if (!this.listeners[type]) {
this.listeners[type] = [];
}
this.listeners[type].push(listener);
}
/**
* 移除消息监听器
* @param {BridgeListener} listener - 要移除的监听器对象
* @returns {void}
*/
removeListener(listener: BridgeListener): void {
const type = listener.listentType;
const listeners = this.listeners[type];
if (listeners) {
const index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
}
}
/**
* 设置原生桥接
* @private
* @returns {void}
*/
private setupNativeBridge(): void {
if (this.nativeBridge) {
this.nativeBridge.onMessage = (message: string) => {
try {
const data = JSON.parse(message) as BridgeDTO;
this.notifyListeners(data);
} catch (error) {
console.error('Failed to parse native message:', error);
}
};
}
}
/**
* 通知所有监听器接收到的消息
* @private
* @param {BridgeDTO} data - 接收到的数据
* @returns {void}
*/
private notifyListeners(data: BridgeDTO): void {
const typeListeners = this.listeners[data.bridgeType];
if (typeListeners) {
typeListeners.forEach(listener => {
listener.onListent(data);
});
}
}
}
import { Bridge, BridgeListener } from './Bridge';
import { BridgeDTO, BridgeEnum, BridgeStatus } from './entities';
/**
* WebSocket桥接实现类,用于通过WebSocket进行通信
* @implements {Bridge}
*/
export class WebSocketBridge implements Bridge {
/** WebSocket连接目标 */
target: WebSocket;
/** 监听器集合,按桥接类型分组 */
listeners: Record<string, BridgeListener[]>;
/** 当前连接状态 */
status: BridgeStatus = BridgeStatus.DISCONNECTED;
/** 连接钩子集合 */
connectHooks: Set<Function> = new Set();
/** 断开连接钩子集合 */
disconnectHooks: Set<Function> = new Set();
/** WebSocket服务器URL */
private url: string;
/**
* 创建WebSocket桥接实例
* @param {string} url - WebSocket服务器URL
*/
constructor(url: string) {
this.url = url;
this.listeners = {};
}
/**
* 建立WebSocket连接
* @returns {void}
*/
connect(): void {
if (this.status === BridgeStatus.CONNECTED) {
return;
}
try {
this.target = new WebSocket(this.url);
this.target.onopen = () => {
this.status = BridgeStatus.CONNECTED;
this.connectHooks.forEach(hook => hook());
};
this.target.onmessage = (event) => {
try {
const data = JSON.parse(event.data) as BridgeDTO;
this.notifyListeners(data);
} catch (error) {
console.error('Failed to parse message:', error);
}
};
this.target.onerror = (error) => {
console.error('WebSocket error:', error);
};
this.target.onclose = () => {
this.status = BridgeStatus.DISCONNECTED;
this.disconnectHooks.forEach(hook => hook());
};
} catch (error) {
console.error('Failed to create WebSocket:', error);
this.status = BridgeStatus.DISCONNECTED;
}
}
/**
* 断开WebSocket连接
* @returns {void}
*/
disconnect(): void {
if (this.target) {
this.target.close();
this.status = BridgeStatus.DISCONNECTED;
this.disconnectHooks.forEach(hook => hook());
}
}
/**
* 发送数据到WebSocket服务器
* @template T - 请求数据类型
* @template O - 响应数据类型
* @param {BridgeDTO<T, O>} data - 要发送的数据
* @returns {void}
*/
send<T, O>(data: BridgeDTO<T, O>): void {
if (this.target && this.target.readyState === WebSocket.OPEN) {
this.target.send(JSON.stringify(data));
} else {
console.error('WebSocket is not connected');
}
}
/**
* 添加连接钩子
* @param {Function} hook - 钩子函数
* @returns {void}
*/
addConnectHook(hook: Function): void {
this.connectHooks.add(hook);
}
/**
* 添加断开连接钩子
* @param {Function} hook - 钩子函数
* @returns {void}
*/
addDisconnectHook(hook: Function): void {
this.disconnectHooks.add(hook);
}
/**
* 添加消息监听器
* @param {BridgeListener} listener - 监听器对象
* @returns {void}
*/
addListener(listener: BridgeListener): void {
const type = listener.listentType;
if (!this.listeners[type]) {
this.listeners[type] = [];
}
this.listeners[type].push(listener);
}
/**
* 移除消息监听器
* @param {BridgeListener} listener - 要移除的监听器对象
* @returns {void}
*/
removeListener(listener: BridgeListener): void {
const type = listener.listentType;
const listeners = this.listeners[type];
if (listeners) {
const index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
}
}
/**
* 通知所有监听器接收到的消息
* @private
* @param {BridgeDTO} data - 接收到的数据
* @returns {void}
*/
private notifyListeners(data: BridgeDTO): void {
const typeListeners = this.listeners[data.bridgeType];
if (typeListeners) {
typeListeners.forEach(listener => {
listener.onListent(data);
});
}
}
}
图示
流程图
细节的类图
核心其实集中在了一个Bridge对象上,重点的数据对象则单独定义.再结合一个工厂模式来创建桥接实例.
你可以右键下面这个图,在新的标签页中打开,这样可以放大和拖动的查看
尾声
通过 UML 类图,我清晰地呈现了一个多端通讯模块的架构设计。该方案以 Bridge 接口为核心,通过工厂模式和标准化数据结构,实现了通讯逻辑的抽象与统一。无论是 WebSocket、iframe 还是 Webview,开发者只需调用统一的接口,就能快速实现跨端通讯,极大地提升了开发效率和代码可维护性。
未来,我们可以进一步扩展该模块,例如支持更多的通讯协议(如 RTC 或 Service Worker),或集成错误重试和超时机制,以应对更复杂的业务场景。
如果有更好建议,或者有你的常见对应实现想要分享,欢迎来到我的github中提交你的issue,或者pr,共同进步.
如果你想复制粘贴使用,也是我的github地址