Typescript:无法导出既是泛型接口又包含其他泛型接口的模块

11
我正在尝试为Bluebird编写一个CommonJS声明文件,它是一个直接导出通用Promise类的Promise库。但是,该库还将几个其他通用类作为静态成员导出(PromiseInspection),似乎无法使用TypeScript进行建模。
编辑:使用示例,以说明模块导出类的工作原理:
import Promise = require('bluebird');
var promise:Promise<number> = Promise.cast(5);
var x:Promise.PromiseInspection<number> = promise.inspect();

我将尝试几种策略 - 下面是简化的示例:

1. 显而易见的方法

declare module "bluebird" {
    class PromiseInspection<T> {
        // ...
    }
    class Promise<T> {
        PromiseInspection: typeof PromiseInspection; // error
        constructor<T>();
        inspect():PromiseInspection<T>; // error
        static cast<U>(value:U):Promise<U>; 
        // ...
    }
    export = Promise;
}

出错并提示“无法将私有类型PromiseInspection用作公共属性”

2. 使用静态接口

declare module "bluebird2" {
    interface PromiseInspection<T> {
        // ...  
    }
    interface Promise<T> {
        constructor<T>();
        inspect():PromiseInspection<T>;
    }
    interface PromiseStatic {
        new<T>();
        PromiseInspection:typeof PromiseInspection;
        cast<U>(value:U):Promise<U>; // error
    }
    export = PromiseStatic;
}

同样失败,但这次私有类型是Promise。
尝试直接从模块导出构造函数。
declare module "bluebird3" {
    export interface PromiseInspection<T> {
        // ...
    }
    export interface Promise<T> {
        constructor<T>();
        inspect():PromiseInspection<T>;
    }

    export new<T>(); // syntax error
    export function cast<U>(value:U):Promise<U>; 
}

这个方法基本可行,但是用构造函数的方式实现是不可能的。

4. 污染命名空间的方式(可行,但有缺陷)

interface PromiseInspection<T> {
    // ...
}
interface Promise<T> {
    constructor<T>();
    inspect():PromiseInspection<T>;
}

declare module "bluebird4" {    
    interface PromiseStatic {
        new<T>():Promise<T>;
        PromiseInspection: typeof PromiseInspection;
        cast<U>(value:U):Promise<U>;
    }
    export = PromiseStatic;
}

虽然可行,但它会使用Promise和PromiseInspection污染全局命名空间。这 也许 可以,但我宁愿避免它,因为在CommonJS中,这通常被认为是不可接受的。

5. 使用声明合并(可以解决90%的问题...)

declare module "bluebird5" {
    module Promise {
        export interface PromiseInspection<T> {
            value(): T;
            // ...
        }
        export
        function cast<U>(value: U): Promise<U> ;
    }

    class Promise<T> {
        new <T> (): Promise <T> ;
        inspect(): Promise.PromiseInspection <T> ;
    }

    export = Promise;
}

快要完成了 - 但现在我不允许用interface Promise<T>替换class Promise<T>,这使得Promise<T>无法扩展。如果我尝试这样做,以下代码会出现问题:

import Promise = require('bluebird');
var x = new Promise<number>();
x.inspect().value().toExponential();

出现错误“无效的'new'表达式”

链接到实际的、正在进行中的bluebird.d.ts - 这个文件当前会污染全局命名空间(使用解决方案4)

是否有更好的方法来处理这个问题,或者我碰到了语言的限制?

2个回答

5

Anders Hejlsberg在CodePlex上发表了一份答案,所以我想在这里添加它。声明合并的解决方案已经接近完美 - 但我还需要一个"var"声明来声明静态接口,因为它是唯一可以接受构造函数的接口。

declare module "bluebird" {
    module Promise {
        export interface PromiseInspection<T> {
            value(): T;
        }
    }
    interface Promise<T> {
        inspect(): Promise.PromiseInspection <T> ;
    }

    var Promise: {
        new<U>(): Promise<U>;
        cast<U>(value: U): Promise<U> ;
    }
    export = Promise;
}

基本上,可以在模块声明中声明接口成员(只要它们仅声明类型即非物理的),可以在主接口中声明实例成员,可以在 var 声明中声明静态函数成员、构造函数和其他“物理”的成员。此外,他的评论是:“以这种方式编写,您有一个单独的声明用于标识符 Promise 的三个含义:作为命名空间(仅包含类型的模块)、作为类型(碰巧是通用的)和作为值。”

嗨Gorgi,BB定义文件的状态如何?我在DefinitelyTyped上没有看到拉取请求,我想使用它。 - Sean Clark Hess
@SeanClarkHess 看起来有人比我先做了。请参见 https://github.com/spion/DefinitelyTyped/blob/master/bluebird/bluebird.d.ts - Gjorgi Kjosev
我的版本在Node中运行更好,可以在此处找到 - https://github.com/spion/DefinitelyTyped/tree/master/bluebird - Gjorgi Kjosev
太棒了。你已经为此提交了拉取请求吗?如果没有,你应该提交!谢谢! - Sean Clark Hess
@SeanClarkHess - 我在使用ts 0.9.5和node时遇到了问题,只是使用返回promise的东西就需要导入promise库。我决定暂时放弃typescript,直到微软解决他们在最新版本中引入的问题。 - Gjorgi Kjosev
糟糕,我现在用Q做的事情很顺利。只是等待到定义更好之后再切换到bluebird。 - Sean Clark Hess

0
看了你的代码,我发现你缺少了一些export语句。下面的代码可以编译,是否适合?
declare module bluebird {
    export class PromiseInspection<T> {
        // ...
    }
    export class Promise<T> {
        constructor<T>();
        inspect():PromiseInspection<T>;
        static all<T>(promises:Promise<T>[]):Promise<T[]>;
    }
}

declare module "bluebird" {
    export = bluebird;
}

虽然我通常更喜欢在定义类型时使用接口,就像#2中一样:

declare module bluebird {
    export interface PromiseInspection<T> {
        // ...
    }
    export interface Promise<T> {
        constructor<T>();
        inspect():PromiseInspection<T>;
    }
    export interface PromiseStatic {
        new<T>();
        all<T>(promises:Promise<T>[]):Promise<T[]>;
    }
}

declare module "bluebird" {
    export = bluebird;
}

如果不行,你尝试过使用另一个 Promises 库作为类型定义的基础吗?你可以看看这个 https://github.com/borisyankov/DefinitelyTyped/blob/master/q/Q.d.ts

大致上它们看起来像这样:

declare function Q<T>(promise: Q.IPromise<T>): Q.Promise<T>;
declare function Q<T>(promise: JQueryPromise<T>): Q.Promise<T>;
declare function Q<T>(value: T): Q.Promise<T>;

declare module Q {
    //… functions etc in here
}

declare module "q" {
    export = Q;
}

1
是的,我实际上使用了 Q 作为基础,但是 Q 将其 Promise 类导出为 Q.Promise,而 Bluebird 导出类构造函数本身... 编辑:我添加了一个链接到实际的 bluebird.d.ts。 - Gjorgi Kjosev
关于缺少的导出语句 - Bluebird不是包含“Promise”类的模块。该模块Promise类,但还具有静态成员PromiseInspection,它本身是一个通用类。我将添加一个使用示例来说明。 - Gjorgi Kjosev
1
添加了用法示例,说明我为什么使用 export = Interfaceexport = Class - Gjorgi Kjosev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接