网站搜索

在使用 AMD 时,如何在 d.ts 文件中引用 TypeScript 枚举?


我想定义一个 TypeScript 接口来表示,比如,一个错误。像这样:

enum MessageLevel {
    Unknown,
    Fatal,
    Critical,
    Error,
    Warning,
    Info,
    Debug
}

interface IMyMessage {
    name: string;
    level: MessageLevel;
    message: string;
}

到目前为止,这样做是可以的。然而,现在(或许)我想在一个 .d.ts 文件中声明那个接口,以便其他人可以用它来进行类型标注。但我不想在 .d.ts 文件中定义枚举,因为那将是实现部分,而不仅仅是类型信息。这个枚举应该放在一个 .ts 文件中,假设叫做 messageLevel.ts:

///<amd-module name='MessageLevel'/>

export enum MessageLevel {
    Unknown,
    Fatal,
    Critical,
    Error,
    Warning,
    Info,
    Debug
}

到此为止,我可以以这种方式在我的 d.ts 类型定义文件中使用它:

import * as ml from "./MessageLevel";

interface IMyMessage {
    name: string;
    level: ml.MessageLevel;
    message: string;
}

我可以让这工作,但我不喜欢在类型文件中导入实现文件的层次混合。我也不喜欢在类型文件中实际实现枚举的想法。

有没有一种干净的方法可以做到这一点,同时严格将实现和声明分开?

答案#1

最佳解决方案可能取决于您是否偏好实际的 JavaScript 变量是数字、字符串或其他类型。如果您不介意使用字符串,可以这样做:

///messagelevel.d.ts
export type MessageLevel = "Unknown" | "Fatal" | "Critical" | "Error";



///main.d.ts
import * as ml from "./MessageLevel";

interface IMyMessage {
    name: string;
    level: ml.MessageLevel;
    message: string;
}

所以最终在 JavaScript 中,它将仅仅以字符串的形式表示,但 TypeScript 会在你将它与列表中不存在的值比较,或者尝试将其赋值给不同的字符串时标记错误。由于这是 JavaScript 本身最接近任何类型枚举的方式(例如,document.createElement("video") 而不是 document.createElement(ElementTypes.VIDEO)),这可能是表达此逻辑的较好方式之一。

答案#2

在过去的几天里,我一直在思考这个问题,也许 const enum,结合联合类型,可能是一个合适的选择。

这种方法依赖于这样一个事实,即您的 API 客户端可以期望某些在您的 API 文件中未明确定义的枚举。

考虑一下这个。首先,API 文件 api.d.ts:

/**
 * @file api.d.ts
 * 
 * Here you define your public interface, to be
 * implemented by one or more modules.
 */


/**
 * An example enum.
 *  
 * The enum here is `const` so that any reference to its
 * elements are inlined, thereby guaranteeing that none of
 * its members are computed, and that no corresponding 
 * JavaScript code is emmitted by the compiler for this
 * type definition file.
 * 
 * Note how this enum is named distinctly from its
 * "conceptual" implementation, `MyEnum`.
 * TypeScript only allows namespace merging for enums
 * in the case where all namespaces are declared in the
 * same file. Because of that, we cannot augment an enum's
 * namespace across different source files (including
 * `.d.ts` files).
 */
export const enum IMyEnum { A }

/**
 * An example interface.
 */
export interface MyInterface {

    /**
     * An example method.
     * 
     * The method itself receives `IMyEnum` only. Unfortunately,
     * there's no way I'm aware of that would allow a forward
     * declaration of `MyEnum`, like one would do in e.g. C++
     * (e.g. declaration vs definition, ODR).
     */
    myMethod(option: IMyEnum): void;
}

以及一个 API 实现,impl.ts:

/**
 * @file impl.ts
 */

/**
 * A runtime "conceptual" implementation for `IMyEnum`.
 */
enum MyEnum {
    // We need to redeclare every member of `IMyEnum`
    // in `MyEnum`, so that the values for each equally named
    // element in both enums are the same.
    // TypeScript will emit something that is accessible at
    // runtime, for example:
    //
    //    MyEnum[MyEnum["A"] = 100] = "A";
    //
    A = IMyEnum.A
}

class MyObject implements IMyInterface {

    // Notice how this union-typed argument still matches its
    // counterpart in `IMyInterface.myMethod`.
    myMethod(option: MyEnum | IMyEnum): void {
        console.log("You selected: " + MyEnum[option]);
    }
}

// ----

var o = new MyObject();
o.myMethod(MyEnum.A);  // ==> You selected: 100
o.myMethod(IMyEnum.A); // ==> You selected: 100

// YAY! (But all this work shouldn't really be necessary, if TypeScript
// was a bit more reasonable regarding enums and type declaration files...)

我做了这个 gist 作为一个例子,以防有人想看看这种方法的实际运用。

相关文章