You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript联合类型函数签名不兼容:WebSocket跨端addEventListener调用报错

跨浏览器与Node.js WebSocket应用的TypeScript类型兼容问题

问题场景

开发跨浏览器与Node.js的TypeScript WebSocket应用时,浏览器端使用原生WebSocket API,Node.js端使用ws库。将WebSocket构造函数声明为WebSocket | import("ws")类型后,调用addEventListener出现类型错误:

示例代码

const WebSocket = typeof window == "object" ? window.WebSocket : <typeof import("ws")>(require("ws"));
let socket = new WebSocket(url);
socket.addEventListener("message", event => { /* ... */ });

错误信息

error TS2349: This expression is not callable.
Each member of the union type '{ (type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; (type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; } | { ...; }' has signatures, but none of those signatures are compatible with each other.

最小复现

提取类型声明后的简化代码同样报错:

interface BrowserWS {
    addEventListener(type: "message", options?: boolean | AddEventListenerOptions): void;
    addEventListener(type: string, options?: boolean | AddEventListenerOptions): void;
}
interface NodeWS {
    addEventListener(type: "message", options?: EventListenerOptions): void;
    addEventListener(type: "close", options?: EventListenerOptions): void;
}
let s: BrowserWS | NodeWS = {addEventListener: () => {}};
s.addEventListener("message");

问题原因

TypeScript对联合类型的函数调用有严格要求:只有当联合类型中所有函数的签名都能被当前调用参数兼容时,才允许调用。这里的核心冲突点:

  1. 重载集合不匹配:BrowserWS的addEventListener支持任意string类型的事件,而NodeWS只支持"message"和"close";反过来NodeWS有"close"的专属重载,BrowserWS没有。
  2. 参数类型差异:BrowserWS的options参数允许boolean类型,而NodeWS只接受EventListenerOptions(AddEventListenerOptions是它的子类型,但boolean不属于这个类型体系)。

即使你调用的是两者都支持的"message"事件,TypeScript也无法找到一个能同时满足两个接口所有重载的调用方式,因此抛出错误。

类型安全的解决方法

1. 运行时类型收窄(推荐)

通过环境判断明确收窄类型,让TypeScript知道当前环境下的具体类型:

const isBrowser = typeof window === "object";
const WebSocketCtor = isBrowser ? window.WebSocket : require("ws") as typeof import("ws");

let socket: WebSocket | import("ws").WebSocket;
if (isBrowser) {
    socket = new WebSocketCtor(url);
    // 这里socket被收窄为浏览器WebSocket类型,可安全调用addEventListener
    socket.addEventListener("message", event => { /* ... */ });
} else {
    socket = new WebSocketCtor(url);
    // Node端ws库的addEventListener调用
    socket.addEventListener("message", event => { /* ... */ });
}

2. 封装统一适配器

定义一个统一的WebSocket接口,然后分别实现浏览器和Node的适配器,屏蔽底层API差异:

interface UnifiedWebSocket {
    onMessage(callback: (data: string | ArrayBuffer) => void): void;
    // 其他统一方法...
}

function createWebSocket(url: string): UnifiedWebSocket {
    if (typeof window === "object") {
        const ws = new window.WebSocket(url);
        return {
            onMessage(callback) {
                ws.addEventListener("message", event => callback(event.data));
            }
        };
    } else {
        const WebSocket = require("ws");
        const ws = new WebSocket(url);
        return {
            onMessage(callback) {
                ws.addEventListener("message", event => callback(event.data));
            }
        };
    }
}

// 使用时无需关心底层类型
const socket = createWebSocket(url);
socket.onMessage(data => { /* ... */ });

3. 类型断言(简单但需谨慎)

如果能确保当前调用在两种环境下都合法,可以用类型断言跳过检查:

(socket as WebSocket).addEventListener("message", event => { /* ... */ });
// 或者
(socket as import("ws").WebSocket).addEventListener("message", event => { /* ... */ });

注意:这种方式需要自己保证运行时的正确性,若后续API变化可能导致隐藏错误。


内容的提问来源于stack exchange,提问作者JBYoshi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 22:20:41