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

如何推断TypeScript中重载回调的参数类型?

类型安全的Node.js风格回调实现方案

你遇到的问题源于TypeScript对重载函数回调的类型推断限制:当你定义重载接口时,传入的回调会被视为兼容所有重载的"最宽"类型,导致data无法在!err时自动窄化为T。以下是几种可行的解决方案:

方案一:使用联合类型回调配合类型守卫

将回调定义为两个函数类型的联合,让TypeScript能通过err的存在性自动窄化data的类型:

// 定义联合类型的回调:要么带错误,要么带数据
type SafeCallback<T> = ((err: Error) => void) | ((err: null, data: T) => void);

const subscribe = <T>(callback: SafeCallback<T>) => {
    let result: T;
    try {
        // 模拟业务逻辑获取结果
        result = { id: "ts-123" } as T;
    } catch (e) {
        // 将捕获到的错误转为Error类型后调用回调
        callback(e as Error);
        return;
    }

    // 无错误时传入null和结果
    callback(null, result);
};

// 使用示例:类型推断正常
subscribe<{ id: string }>((err, data) => {
    if (err) {
        console.error("出错了:", err.message);
    } else {
        // 这里data会被自动推断为{ id: string },无unknown报错
        console.log("数据ID:", data.id);
    }
});

方案二:单一函数类型配合严格空值检查

如果偏好单一函数签名,可通过Error | null作为错误类型,结合严格空值检查来实现类型安全:

type SafeCallback<T> = (err: Error | null, data: T | undefined) => void;

const subscribe = <T>(callback: SafeCallback<T>) => {
    let result: T;
    try {
        result = { id: "ts-456" } as T;
    } catch (e) {
        callback(e as Error, undefined);
        return;
    }

    callback(null, result);
};

subscribe<{ id: string }>((err, data) => {
    if (err === null) {
        // 结合非空断言或额外判断确保data存在
        console.log("数据ID:", data!.id);
        // 更严谨的写法:
        // if (data) console.log(data.id);
    } else {
        console.error("出错了:", err.message);
    }
});

方案三:利用条件类型实现强关联

通过TypeScript条件类型,让data的类型完全依赖于err的类型,确保无错误时data必然为T:

// 条件类型:若err为null/undefined,则data为T;否则data为never
type SafeCallback<T> = <Err>(err: Err, data: Err extends null | undefined ? T : never) => void;

const subscribe = <T>(callback: SafeCallback<T>) => {
    let result: T;
    try {
        result = { id: "ts-789" } as T;
    } catch (e) {
        callback(e as Error, undefined as never);
        return;
    }

    callback(null, result);
};

subscribe<{ id: string }>((err, data) => {
    if (!err) {
        console.log("数据ID:", data.id); // data类型自动推断为{ id: string }
    } else {
        console.error("出错了:", err.message);
    }
});

为什么原方案失效?

你之前的接口重载写法中,TypeScript会将传入的回调视为兼容所有重载的签名,即(err: unknown | undefined, data?: T) => void,导致data的类型被合并为unknown | T,无法通过!err的判断自动窄化。而联合类型或条件类型的写法,能让TypeScript更精准地关联err和data的类型关系。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 02:10:34