[英]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 條途徑是:
打字稿的類型系統可以表達其中的任何一個嗎?
如果我刪除 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
字符串文字類型,但根據定義,它也將允許字符串文字類型的聯合(畢竟字符串文字類型的聯合集合包含在所有字符串的集合中)
我們可以創建一個自定義錯誤,如果它檢測到字符串文字類型的聯合,則強制調用處於錯誤狀態。 這種檢查可以使用條件類型來完成,確保K
與UnionToIntersection<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.