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

如何为包装对象的JavaScript类编写TypeScript类型声明

问题

我有一个用于包装对象并提供额外属性的JavaScript类ApiObj,希望为其编写TypeScript类型声明,以在类型中体现该包装功能。

简化示例类代码:

// apiObj.js
class ApiObj {
    constructor(obj) {
        for (const [key, val] of Object.entries(obj)) {
            this[key] = val
        }
    }

    get myAdditionalFeature() {
       return "this is an example"
    }
}

使用方式:

const myObj = { a: "test" }
const apiWrapper = new ApiObj(myObj)
const valA = apiWrapper.a // "test"
const valAdditionalFeature = apiWrapper.myAdditionalFeature // "this is an example"

apiWrapper同时具备原对象的属性a和额外属性myAdditionalFeature。

我尝试了两种方法但均存在问题:

  • 方法1:让ApiObj<T>继承T
// apiObj.d.ts
declare class ApiObj<T> extends T {
    // 无法识别属性a,仅能识别ApiObj新增的属性
    constructor(obj: T)
    get myAdditionalFeature(): string
}

问题:TypeScript不允许类泛型继承泛型参数T(除非T是具体类类型),接口或对象类型不能作为类的基类,导致无法正确推断原对象属性。

  • 方法2:使用类型交叉别名
// apiObj.d.ts
declare class ApiObj<T> {
    constructor(obj: T)
    get myAdditionalFeature(): string
}

type ApiObj<T> = ApiObj<T> & T // 类型交叉,看似能生成正确类型

问题:同名的类声明和类型别名会冲突,TypeScript优先使用类类型,导致无法访问原对象的属性。

临时解决方案是通过工厂函数实现,但只能通过工厂函数实例化对象,无法为ApiObj的构造函数实现对应类型:

// apiObj.d.ts
declare class $ApiObj {
    get myAdditionalFeature(): string
}

type ApiObj<T> = $ApiObj & T

declare function apiObj<T extends Object>(obj: T): ApiObj<T>

使用方式:

// usage.ts
interface Test {
    a: string
}
const myTest: Test = {
    a: "Test"
}

const t = apiObj(myTest)
t.a // 正常
t.myAdditionalFeature // 正常

请问该功能能否通过TypeScript实现?若可以,正确的实现方式是什么?

解决方案

可以实现,核心思路是利用构造签名的类型交叉,让ApiObj的构造函数返回交叉类型,同时避免类声明与类型别名的名称冲突。

正确的类型声明方式

// apiObj.d.ts
// 声明ApiObj的基础类,仅包含额外属性
declare class ApiObjBase {
    get myAdditionalFeature(): string;
}

// 定义构造函数类型:接收T类型参数,返回ApiObjBase与T的交叉类型
type ApiObjConstructor = {
    new <T extends object>(obj: T): ApiObjBase & T;
};

// 将全局的ApiObj指向这个构造函数类型
declare const ApiObj: ApiObjConstructor;

使用验证

// usage.ts
interface Test {
    a: string;
}
const myTest: Test = { a: "Test" };

const myApi = new ApiObj(myTest);
myApi.a; // 类型正确,string
myApi.myAdditionalFeature; // 类型正确,string

原理说明

  • 用ApiObjBase单独定义类的固有属性,避免与交叉类型的名称冲突;
  • 通过构造签名new <T>(obj: T): ApiObjBase & T,告知TypeScript:调用new ApiObj(obj)时,返回的实例同时拥有obj的所有属性和ApiObjBase的属性;
  • 这种方式完全匹配JavaScript类的运行时行为,既保留了new关键字实例化的方式,又能正确推断原对象的属性类型。

简化写法

如果不想单独声明ApiObjBase,也可以直接在构造签名中定义额外属性:

// apiObj.d.ts
type ApiObjExtra = {
    get myAdditionalFeature(): string;
};

declare const ApiObj: {
    new <T extends object>(obj: T): T & ApiObjExtra;
};

使用效果与上述方案完全一致,代码更简洁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 16:35:20