簡體   English   中英

在 typescript 中獲取枚舉鍵作為聯合字符串的通用類型?

[英]Generic type to get enum keys as union string in typescript?

考慮以下 typescript 枚舉:

enum MyEnum { A, B, C };

如果我想要另一種類型,即該枚舉的鍵的聯合字符串,我可以執行以下操作:

type MyEnumKeysAsStrings = keyof typeof MyEnum;  // "A" | "B" | "C"

這非常有用。

現在我想創建一個通用類型,以這種方式對枚舉進行通用操作,這樣我就可以說:

type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<MyEnum>;

我想正確的語法是:

type AnyEnumKeysAsStrings<TEnum> = keyof typeof TEnum; // TS Error: 'TEnum' only refers to a type, but is being used as a value here.

但這會產生一個編譯錯誤:“'TEnum' 只引用一個類型,但在這里被用作一個值。”

這是出乎意料和悲哀的。 我可以通過從泛型聲明的右側刪除 typeof 並將其添加到特定類型聲明中的類型參數來通過以下方式不完全解決它:

type AnyEnumAsUntypedKeys<TEnum> = keyof TEnum;
type MyEnumKeysAsStrings = AnyEnumAsUntypedKeys<typeof MyEnum>; // works, but not kind to consumer.  Ick.

不過,我不喜歡這種解決方法,因為這意味着消費者必須記住對泛型進行這種討厭的 typeof 指定。

是否有任何語法可以讓我按照最初的意願指定泛型類型,以善待消費者?

不,消費者需要使用typeof MyEnum來引用鍵為ABC的對象。


前面有很長的解釋,其中一些你可能已經知道了

您可能知道,TypeScript 向 JavaScript 添加了一個靜態類型系統,並且在代碼被轉譯時該類型系統會被刪除 TypeScript 的語法是這樣的,一些表達式和語句引用運行時存在的,而其他表達式和語句引用僅在設計/編譯時存在的類型 具有類型,但它們本身不是類型。 重要的是,在代碼中的某些地方,編譯器會期望一個值並將它找到的表達式解釋為一個值,如果可能的話,編譯器會期望一個類型並將它找到的表達式解釋為一個類型。

如果一個表達式可能被解釋為一個值和一個類型,編譯器不會在意或感到困惑。 例如,對於以下代碼中的兩種類型的null ,它非常高興:

let maybeString: string | null = null;

null的第一個實例是一個類型,第二個是一個值。 它也沒有問題

let Foo = {a: 0};
type Foo = {b: string};   

其中第一個Foo是一個命名值,第二個Foo是一個命名類型。 請注意,值Foo的類型是{a: number} ,而類型Foo{b: string} 他們不一樣。

即使是typeof運算符也有雙重的生活。 表達式typeof x總是期望x是一個值,但typeof x本身可以是一個值或類型,具體取決於上下文:

let bar = {a: 0};
let TypeofBar = typeof bar; // the value "object"
type TypeofBar = typeof bar; // the type {a: number}

let TypeofBar = typeof bar; 將通過 JavaScript,它會在運行時使用JavaScript typeof 運算符並生成一個字符串。 但是type TypeofBar = typeof bar ; 被刪除,它使用TypeScript 類型查詢運算符來檢查 TypeScript 分配給名為bar的值的靜態類型。


現在,TypeScript 中大多數引入名稱的語言結構都創建了命名值或命名類型。 下面是一些命名值的介紹:

const value1 = 1;
let value2 = 2;
var value3 = 3;
function value4() {}

以下是命名類型的一些介紹:

interface Type1 {}
type Type2 = string;

但是有一些聲明同時創建命名值命名類型,並且像上面的Foo一樣,命名值的類型不是命名類型 最大的是classenum

class Class { public prop = 0; }
enum Enum { A, B }

這里,類型ClassClass實例的類型,而Class構造函數對象。 並且typeof Class不是Class

const instance = new Class();  // value instance has type (Class)
// type (Class) is essentially the same as {prop: number};

const ctor = Class; // value ctor has type (typeof Class)
// type (typeof Class) is essentially the same as new() => Class;

並且, Enum類型是枚舉元素的類型; 每個元素的類型的聯合。 Enum是一個對象,其鍵是AB ,其屬性是枚舉的元素。 並且typeof Enum不是Enum

const element = Math.random() < 0.5 ? Enum.A : Enum.B; 
// value element has type (Enum)
// type (Enum) is essentially the same as Enum.A | Enum.B
//  which is a subtype of (0 | 1)

const enumObject = Enum;
// value enumObject has type (typeof Enum)
// type (typeof Enum) is essentially the same as {A: Enum.A; B: Enum.B}
//  which is a subtype of {A:0, B:1}

現在支持您的問題。 你想發明一個像這樣工作的類型運算符:

type KeysOfEnum = EnumKeysAsStrings<Enum>;  // "A" | "B"

類型Enum放入其中,然后取出對象Enum的鍵。 但正如您在上面看到的,類型Enum與對象Enum不同 不幸的是,該類型對值一無所知。 這有點像這樣說:

type KeysOfEnum = EnumKeysAsString<0 | 1>; // "A" | "B"

顯然,如果你這樣寫,你會發現你對類型0 | 1無能為力。 0 | 1將產生類型"A" | "B" "A" | "B" 為了讓它工作,你需要傳遞一個知道映射的類型。 那個類型是typeof Enum ...

type KeysOfEnum = EnumKeysAsStrings<typeof Enum>; 

這就像

type KeysOfEnum = EnumKeysAsString<{A:0, B:1}>; // "A" | "B"

可能的......如果type EnumKeysAsString<T> = keyof T


所以你堅持讓消費者指定typeof Enum 有解決方法嗎? 好吧,您也許可以使用具有該值的東西,例如函數?

 function enumKeysAsString<TEnum>(theEnum: TEnum): keyof TEnum {
   // eliminate numeric keys
   const keys = Object.keys(theEnum).filter(x => 
     (+x)+"" !== x) as (keyof TEnum)[];
   // return some random key
   return keys[Math.floor(Math.random()*keys.length)]; 
 }

然后你可以打電話

 const someKey = enumKeysAsString(Enum);

並且someKey的類型將是"A" | "B" "A" | "B" 是的,但是要使用它作為類型,你必須查詢它:

 type KeysOfEnum = typeof someKey;

這迫使您再次使用typeof並且比您的解決方案更冗長,特別是因為您不能這樣做:

 type KeysOfEnum = typeof enumKeysAsString(Enum); // error

布萊。 對不起。


回顧:

  • 這是不可能的;
  • 類型和價值 blah blah;
  • 仍然不可能;
  • 對不起。

希望這有點道理。 祝你好運。

這實際上是可能的。

enum MyEnum { A, B, C };

type ObjectWithValuesOfEnumAsKeys = { [key in MyEnum]: string };

const a: ObjectWithValuesOfEnumAsKeys = {
    "0": "Hello",
    "1": "world",
    "2": "!",
};

const b: ObjectWithValuesOfEnumAsKeys = {
    [MyEnum.A]: "Hello",
    [MyEnum.B]: "world",
    [MyEnum.C]: "!",
};

// Property '2' is missing in type '{ 0: string; 1: string; }' but required in type 'ObjectWithValuesOfEnumAsKeys'.
const c: ObjectWithValuesOfEnumAsKeys = {  //  Invalid! - Error here!
    [MyEnum.A]: "Hello",
    [MyEnum.B]: "world",
};

// Object literal may only specify known properties, and '6' does not exist in type 'ObjectWithValuesOfEnumAsKeys'.
const d: ObjectWithValuesOfEnumAsKeys = {
    [MyEnum.A]: "Hello",
    [MyEnum.B]: "world",
    [MyEnum.C]: "!",
    6: "!",  //  Invalid! - Error here!
};

游樂場鏈接


編輯:解除限制!

enum MyEnum { A, B, C };

type enumValues = keyof typeof MyEnum;
type ObjectWithKeysOfEnumAsKeys = { [key in enumValues]: string };

const a: ObjectWithKeysOfEnumAsKeys = {
    A: "Hello",
    B: "world",
    C: "!",
};

// Property 'C' is missing in type '{ 0: string; 1: string; }' but required in type 'ObjectWithValuesOfEnumAsKeys'.
const c: ObjectWithKeysOfEnumAsKeys = {  //  Invalid! - Error here!
    A: "Hello",
    B: "world",
};

// Object literal may only specify known properties, and '6' does not exist in type 'ObjectWithValuesOfEnumAsKeys'.
const d: ObjectWithKeysOfEnumAsKeys = {
    A: "Hello",
    B: "world",
    C: "!",
    D: "!",  //  Invalid! - Error here!
};

游樂場鏈接


  • 這也適用於const enum

這是不需要創建新的泛型類型的解決方案。

如果你聲明一個枚舉

enum Season { Spring, Summer, Autumn, Winter };

要獲得類型,您只需要使用關鍵字keyof typeof

let seasonKey: keyof typeof Season;

然后變量按預期工作

seasonKey = "Autumn"; // is fine
// seasonKey = "AA" <= won't compile

你可以只傳遞一個type而不是一個value ,編譯器不會抱怨。 正如您所指出的,您可以通過typeof實現這一點。
將不那么自動化:

type AnyEnumKeysAsStrings<TEnumType> = keyof TEnumType;

您可以將其用作:

type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<typeof MyEnum>;

如果我正確理解了 OP 問題並且 Akxe 回答:這是一個可能的進一步簡化。 使用 typescript 類型的實用程序。 記錄<鍵,類型>

https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type

例如

enum MyEnum { A, B, C };

type enumValues = keyof typeof MyEnum;
type ObjectWithKeysOfEnumAsKeys = Record<enumValues, string>
const a: ObjectWithKeysOfEnumAsKeys = {
    A: "PropertyA",
    B: "PropertyB",
    C: "PropertyC",
};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM