typescript:從介面建立 [key,value 類型] 的元組
我正在嘗試創建一個 TypeScript 元組類型(一對),它只能包含第一個值是某個接口的鍵(該接口以有效操作作為鍵,對應值為預期參數的類型)的值
我想下面的程式碼會更好地說明我想要達成的目標:
interface IEventsAndArguments {
'+': number,
'-': number,
'change_name': string,
}
// Now I want to produce tuples,
// where the first value is in keyof IEventsAndArguments
// and the second value is of the corresponding type
// (but a type with 2 properties name and value would do as well)
// it works for function arguments
function myFunction<K extends keyof IEventsAndArguments>(x:K, y:IEventsAndArguments[K]) {}
myFunction('+', 'a string'); // invalid as expected, '+' needs a number
myFunction('+', 4);
myFunction('change_name', 'John');
function myFunction2<K extends keyof IEventsAndArguments>({x, y}:{x:K, y:IEventsAndArguments[K]}) {}
myFunction2({x: '+', y: 'another string'}); // invalid as expected
myFunction2({x: '-', y: 3});
// it doesn't work when I try to define a tuple type
type TTuple<K extends keyof IEventsAndArguments> = [K, IEventsAndArguments[K]];
// I would like the next tuple to be INVALID
// but it is valid since the second element is of type number | string
const tuple1:TTuple<keyof IEventsAndArguments> = [ '+', 'hello' ]
// What I ultimately would want is something similar to this
type TTuple2<K extends keyof IEventsAndArguments> = [
K,
(v:IEventsAndArguments[K]) => void
];
// I would like the next tuple to be VALID but it is invalid
// since the function argument must be of type number | string
const tuple2:TTuple2<keyof IEventsAndArguments> = [
'+',
(t:number) => {},
];
這裡有一個包含相同程式碼的遊樂場連結。
有沒有辦法讓這個運作?有人能解釋為什麼它在函數上表現如我所願,但在定義新類型時卻不行嗎?
謝謝。
編輯:我想我找到了一个“足夠好”的機制:既然它對函數有效,我們可以用函數來產生一個有效的元組!
// if we use a function to produce the tuple, we can produce 'valid'
// tuples, even if the tuple type itself is not as strict as we'd like...
function tuple<K extends keyof IEventsAndArguments>(
a:K, f:(v:IEventsAndArguments[K]) => void
):TTuple2<K>
{
return [a, f];
}
const tuple3 = tuple('+', (v:number) => v + 1); // valid, as expected
const tuple4 = tuple('+', (v:string) => v + 1); // invalid, also as expected
但挫折感依然存在:為什麼它對函數有效(參數都『綁定』到介面的同一個條目),但在創建新類型時卻無效?如果我們也能以某種方式將類型設置得盡可能嚴格,那會很好,因為現在你仍然可以通過不使用函數來生成『無效』的元組。
哦:另外很重要的一點:我希望這是通用的,所以無論人們之後在介面中『添加』什麼條目,它都應該能繼續運作,因此應假設 IEventsAndArguments 的定義是未知的,並且可以在其他地方被擴展。
答案#1
你希望你的 TTuple 類型要麼是形式為 ["+", (v: number) => void] 的元組,要麼是形式為 ["-", (v: number) => void] 的元組,要麼是形式為 ["change_name", (v: string) => void] 的元組。你可以不用泛型,只需將 TTuple 定義為這些類型的聯合即可達到這個目的:
type TTuple =
["+", (v: number) => void] |
["-", (v: number) => void] |
["change_name", (v: string) => void];
你可以確認這個有效:
let tuple: TTuple;
tuple = ['+', (t: number) => console.log(t.toFixed())]; // okay
tuple = ['change_name', (t: number) => console.log(t.toFixed())]; // error
tuple = ['change_name', (t: string) => console.log(t.toUpperCase())]; // okay
當然,你不想手動定義TTuple;最好讓編譯器將TTuple作為IEventsAndArguments的函數來計算。
其中一種方法是創建所謂的「分配物件類型」(術語來自 microsoft/TypeScript#47109),你首先對 IEventsAndArguments 類型進行映射,以形成具有相同鍵的新物件類型,但其中屬性值類型是對應的元組類型,然後你立即使用 IEventsAndArguments 鍵的聯合類型來索引這個映射類型,這會產生元組的聯合類型。看起來如下:
type TTuple = { [K in keyof IEventsAndArguments]: [
K,
(v: IEventsAndArguments[K]) => void
] }[keyof IEventsAndArguments];
通過那個,映射類型 {[K in keyof]... } 會遍歷 "+"、"-" 和 "change_name" 鍵以生成類似的東西
{
"+": ["+", (v: IEventsAndArguments["+"] )=> void],
"-": ["-", (v: IEventsAndArguments["-"] )=> void],
"change_name": ["change_name", (v: IEventsAndArguments["change_name"] )=> void]
}
化簡為
{
"+": ["+", (v: number) => void],
"-": ["-", (v: number) => void],
"change_name": ["change_name", (v: string) => void]
}
然後我們用 "+" | "-" | "change_name" 來索引它,這會產生所需的聯合型 ["+", (v: number) => void] | ["-", (v: number) => void] | ["change_name", (v: string) => void]。
現在,如果你在 IEventsAndArguments 類型上添加或更改屬性,TTuple 類型將會自動做出相應的更改以適應它。
程式碼的 Playground 連結
