[英]Constraining type in Typescript generic to be one of several types
我試圖將泛型的輸入限制為幾種類型之一。 我發現的最接近的表示法是使用聯合類型。 這是一個簡單的示例:
interface IDict<TKey extends string | number, TVal> {
// Error! An index signature parameter type must be
// a 'string' or a 'number'
[key: TKey]: TVal;
}
declare const dictA: IDict<string, Foo>;
declare const dictB: IDict<number, Foo>;
在此示例中,我要尋找的是一種表示TKey
應該為string
或number
而不是它們的並集的方式。
有什么想法嗎?
注意:這是一個更廣泛的問題的特定情況。 例如,在另一種情況下,我有一個函數接受text
,該text
可以是string
,也可以是StructuredText
(解析的Markdown),將其轉換並精確返回相應的類型(而不是子類型)。
function formatText<T extends string | StructuredText>(text: T): T {/*...*/}
從技術上講,我可以將其寫為重載,但這似乎不是正確的方法。
function formatText(text: string): string;
function formatText(text: StructuredText): StructuredText;
function formatText(text) {/*...*/}
重載也被證明是有問題的,因為它不接受聯合類型:
interface StructuredText { tokens: string[] }
function formatText(txt: string): string;
function formatText(txt: StructuredText): StructuredText;
function formatText(text){return text;}
let s: string | StructuredText;
let x = formatText(s); // error
於2019-06-20更新為TS3.5 +
K extends string | number
K extends string | number
為指數的簽名參數: 是的,這不能以令人滿意的方式完成。 有幾個問題。 首先是TypeScript僅識別兩種直接的索引簽名類型: [k: string]
和[k: number]
。 而已。 您不能將它們(無[k: string | number]
)或它們的子類型(無[k: 'a'|'b']
)進行聯合,甚至也不能將它們作為別名:(無[k: s]
,其中type s = string
)。
第二個問題是, number
為索引類型是不能很好地推廣到打字稿其余一個奇怪的特例。 在JavaScript中,所有對象索引在使用前都會轉換為它們的字符串值。 這意味着a['1']
和a[1]
是相同的元素。 因此,從某種意義上說,作為索引的number
類型更像是string
的子類型。 如果您願意放棄number
文字,而是將其轉換為string
文字,那么您會更輕松。
如果是這樣,則可以使用映射類型來獲取所需的行為。 實際上, 標准庫中包含一種稱為Record<>
的類型,正是我建議使用的類型:
type Record<K extends string, T> = {
[P in K]: T;
};
type IDict<TKey extends string, TVal> = Record<TKey, TVal>
declare const dictString: IDict<string, Foo>; // works
declare const dictFooBar: IDict<'foo' | 'bar', Foo>; // works
declare const dict012: IDict<'0' | '1' | '2', Foo>; // works
dict012[0]; // okay, number literals work
dict012[3]; // error
declare const dict0Foo: IDict<'0' | 'foo',Foo>; // works
非常接近工作。 但:
declare const dictNumber: IDict<number, Foo>; // nope, sorry
缺少的number
開始工作將是類似numericString
的類型,定義如下
type numericString = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7' // ... etc etc
然后可以使用IDict<numericString, Foo>
,其行為類似於您希望IDict<number, Foo>
的行為。 沒有這樣的類型,強制TypeScript執行此操作就沒有多大意義。 我建議您放棄,除非您有非常引人注目的用例。
我想我明白你在這里想要什么。 這個想法是,您想要一個函數,該函數的參數類型可以擴展像string | number
string | number
,但是它應該返回一個擴展為該聯合的一個或多個元素的類型。 您正在嘗試避免子類型出現問題。 因此,如果參數為1
,則您不想承諾輸出1
,而只是輸出一個number
。
在此之前,我要說的只是使用重載:
function zop(t: string): string; // string case
function zop(t: number): number; // number case
function zop(t: string | number): string | number; // union case
function zop(t: string | number): string | number { // impl
return (typeof t === 'string') ? (t + "!") : (t - 2);
}
這表現為您想要的:
const zopNumber = zop(1); // return type is number
const zopString = zop('a'); // return type is string
const zopNumberOrString = zop(
Math.random()<0.5 ? 1 : 'a'); // return type is string | number
如果您的工會中只有兩種類型,這就是我的建議。 但是,這對於較大的聯合(例如, string | number | boolean | StructuredText | RegExp
)可能會變得笨拙,因為您需要為聯合中元素的每個非空子集包括一個重載簽名。
除了重載,我們可以使用條件類型 :
// OneOf<T, V> is the main event:
// take a type T and a tuple type V, and return the type of
// T widened to relevant element(s) of V:
type OneOf<
T,
V extends any[],
NK extends keyof V = Exclude<keyof V, keyof any[]>
> = { [K in NK]: T extends V[K] ? V[K] : never }[NK];
下面是它的工作原理:
declare const str: OneOf<"hey", [string, number, boolean]>; // string
declare const boo: OneOf<false, [string, number, boolean]>; // boolean
declare const two: OneOf<1 | true, [string, number, boolean]>; // number | boolean
這是聲明函數的方法:
function zop<T extends string | number>(t: T): OneOf<T, [string, number]>;
function zop(t: string | number): string | number { // impl
return (typeof t === 'string') ? (t + "!") : (t - 2);
}
它的行為與以前相同:
const zopNumber = zop(1); // 1 -> number
const zopString = zop('a'); // 'a' -> string
const zopNumberOrString = zop(
Math.random()<0.5 ? 1 : 'a'); // 1 | 'a' -> string | number
ew。 希望能有所幫助; 祝好運!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.