簡體   English   中英

打字稿將泛型約束為字符串文字類型以用於計算對象屬性

[英]Typescript constrain generic to string literal type for use in computed object property

我正在嘗試編寫一個函數,該函數將接受一個字符串文字並返回一個帶有單個字段的對象,該字段的名稱是該字符串文字。 我可以編寫一個執行我想要的函數的函數,但我不知道如何表達其參數類型為字符串文字的約束。

我得到的最接近的是使用擴展string的泛型類型。 這允許字符串文字類型,但也允許字符串文字類型和類型string ,我不想將其傳遞給我的函數。

只要K是字符串文字類型,就可以編譯並執行我想要的操作。 請注意,類型斷言在 typescript 3.4 中不是必需的,但在 3.5 中是必需的。

function makeObject<K extends string>(key: K): { [P in K]: string } {
  return { [key]: "Hello, World!" } as { [P in K]: string };
}

如果K不是字符串文字,則此函數的返回類型將與它返回的值的類型不同。

我可以想象的實現這項工作的 2 條途徑是:

  • 將 K 限制為僅字符串文字
  • 表示返回類型是具有單個字段的對象,其名稱是 K 中的值(不太令人滿意,但至少函數的類型是誠實的)

打字稿的類型系統可以表達其中的任何一個嗎?

如果我刪除 typescript 3.5 中的類型斷言,我會收到錯誤消息:

a.ts:2:3 - error TS2322: Type '{ [x: string]: string; }' is not assignable to type '{ [P in K]: string; }'.

2   return { [key]: "Hello, World!" };

對於單個字符串文字類型沒有限制。 如果您指定extends string ,編譯器將推斷K字符串文字類型,但根據定義,它也將允許字符串文字類型的聯合(畢竟字符串文字類型的聯合集合包含在所有字符串的集合中)

我們可以創建一個自定義錯誤,如果它檢測到字符串文字類型的聯合,則強制調用處於錯誤狀態。 這種檢查可以使用條件類型來完成,確保KUnionToIntersection<K>相同。 如果這是真的K不是聯合,因為'a' extends 'a''a' | 'b' 'a' | 'b'不擴展'a' & 'b'

type UnionToIntersection<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type CheckForUnion<T, TErr, TOk> = [T] extends [UnionToIntersection<T>] ? TOk : TErr

function makeObject<K extends string>(key: K & CheckForUnion<K, never, {}>): { [P in K]: string } {
    return { [key]: "Hello, World!" } as { [P in K]: string };
}

makeObject("a")
makeObject("a" as "a" | "b") // Argument of type '"a" | "b"' is not assignable to parameter of type 'never'

TypeScript 4.2 更新

以下工作:

type StringLiteral<T> = T extends string ? string extends T ? never : T : never;

(不再有效):TypeScript 4.1 模板文字類型技巧

編輯:下面的內容實際上是在 4.2 中出現的。 在這里討論

type StringLiteral<T> = T extends `${string & T}` ? T : never;

TS 4.1 引入了模板文字類型,允許您將字符串文字轉換為其他字符串文字。 您可以將字符串文字轉換為自身。 由於只能對文字進行模板化,而不能對一般字符串進行模板化,因此您只需有條件地檢查字符串文字是否從其自身擴展。

完整示例:

type StringLiteral<T> = T extends `${string & T}` ? T : never;

type CheckLiteral = StringLiteral<'foo'>;  // type is 'foo'
type CheckString = StringLiteral<string>;  // type is never

function makeObject<K>(key: StringLiteral<K>) {
    return { [key]: 'Hello, World!' } as { [P in StringLiteral<K>]: string };
}

const resultWithLiteral = makeObject('hello');  // type is {hello: string;}
let someString = 'prop';
const resultWithString = makeObject(someString); // compiler error.

我認為K聯合不再是一個問題,因為沒有必要縮小makeObject簽名中屬性鍵的類型。 如果有的話,這會變得更加靈活。

暫無
暫無

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

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