簡體   English   中英

Typescript泛型中的約束類型為幾種類型之一

[英]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應該為stringnumber而不是它們的並集的方式。

有什么想法嗎?

注意:這是一個更廣泛的問題的特定情況。 例如,在另一種情況下,我有一個函數接受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 +

問題#1: 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執行此操作就沒有多大意義。 我建議您放棄,除非您有非常引人注目的用例。

問題2:泛型可以從列表擴展為某種類型:

我想我明白你在這里想要什么。 這個想法是,您想要一個函數,該函數的參數類型可以擴展像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.

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