たとえば、エラーを表す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;
}
私はこれを機能させることができますが、実装ファイルをタイピングファイルにインポートするレベルミキシングは好きではありません。また、タイピングファイルに列挙型を実際に実装するというアイデアも好きではありません。
実装と宣言を厳密に分離しておく、これを行うためのクリーンな方法はありますか?
最善の解決策は、実際のJavaScript変数が数値、文字列、またはその他であるかどうかによって異なります。 Stringを気にしない場合は、次のように実行できます。
_///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)
であるため、このロジックを表現するためのより良い方法の1つである可能性があります。
私はこの数日間この問題について考えていました、そしておそらく 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...)
私は例としてこの要点を作成しました 誰かがこのアプローチの実際を見てみたい場合に備えて。