我有一個簡單的函式,它檢查是否variable包含在陣列中opts
type IType1 = 'text1' | 'text2';
type IType2 = 'text3' | 'text4' | 'text5';
const foo: IType2 = 'text4';
function oneOf(variable, opts) {
return opts.includes(variable);
}
我想要的是使optsandvariable相互依賴,所以如果我呼叫該函式:
oneOf(foo, ['text3', 'text5']) //=> I would get OK
oneOf(foo, ['text3', 'text2']) //=> I would get a warning here, because `IType2` (type of `foo`) does not contain 'text2'
方法一
如果我寫:
function oneOf<T extends IType1 | IType2>(variable: T, opts: T[]): boolean{
return opts.includes(variable);
}
在這兩種情況下我都會得到OK。TS 只會假設在第二種情況下T extends "text3" | "text4" | "text2",這不是我想要的。
方法二
如果我寫
function oneOf<T1 extends IType1 | IType2, T2 extends T1>(variable: T1, opts: T2[]): boolean{
return opts.includes(variable);
}
我會收到一個錯誤:
Argument of type 'T1' is not assignable to parameter of type 'T2'.
'T1' is assignable to the constraint of type 'T2', but 'T2' could be instantiated with a
different subtype of constraint '"text1" | "text2" | "text3" | "text4" | "text5"'.
...
這可以在TS中完成嗎?
uj5u.com熱心網友回復:
約束 的問題T extends IType1 | IType2在于IType1,IType2它們本身就是字串文字型別的聯合。編譯器會將其折疊為 just ,因此編譯器不會自然地根據and對它們進行分組。如果你傳入一個型別的值,那么編譯器會推斷出is和 not 。T extends 'text1' | 'text2' | 'text3' | 'text4' | 'text5'IType1IType2'text4'T'text4'IType2
所以有不同的方法來解決這個問題。一種是您可以將該聯合型別保留在 of 型別的約束中,但對 of 元素的型別variable使用條件型別opts:
function oneOf<T extends IType1 | IType2>(
variable: T,
opts: Array<T extends IType1 ? IType1 : IType2>
): boolean {
return (opts as readonly (IType1 | IType2)[]).includes(variable);
}
oneOf('text4', ['text3', 'text5']) // okay
oneOf('text4', ['text3', 'text2']) // error
條件型別T extends IType1 ? IType1 : IType2具有從擴大T到IType1或的效果IType2。請注意,該函式的實作至少需要一個型別斷言,因為編譯器實際上無法對未決議的泛型型別做太多事情……因為它不知道是什么T,所以它無法弄清楚是什么T extends IType1 ? IType1 : IType2;這種型別對編譯器本質上是不透明的,因為評估被推遲了。通過說opts是readonly (IType1 | IType2)[]我們只是說無論opts是什么,我們都能夠讀取型別的元素IType1或IType2從中讀取元素。
解決此問題的另一種方法是放棄泛型,而只考慮 and 的不同呼叫IType1簽名IType2。傳統上你會用這樣的多載來做到這一點:
function oneOf(variable: IType1, opts: IType1[]): boolean;
function oneOf(variable: IType2, opts: IType2[]): boolean;
function oneOf(variable: IType1 | IType2, opts: readonly (IType1 | IType2)[]) {
return opts.includes(variable);
}
oneOf('text4', ['text3', 'text5']); // okay
oneOf('text4', ['text3', 'text2']); // error
這很好,但是多載可能有點難以處理,并且它們的擴展性不是很好(如果你有IType1 | IType2 | IType3 | ... | IType10寫出呼叫簽名可能會很煩人)。
當每個呼叫簽名的回傳型別相同(就像這些都是boolean)時,多載的替代方法是有一個呼叫簽名,它接受一個型別為元組聯合的rest 引數:
function oneOf(...[variable, opts]:
[variable: IType1, opts: IType1[]] |
[variable: IType2, opts: IType2[]]
): boolean {
return (opts as readonly (IType1 | IType2)[]).includes(variable);
}
oneOf('text4', ['text3', 'text5']); // okay
oneOf('text4', ['text3', 'text2']); // error
從呼叫者的角度來看,這實際上看起來很像過載。從中您可以制作一個程式版本,為我們計算元組的并集:
type ValidArgs<T extends any[]> = {
[I in keyof T]: [variable: T[I], opts: T[I][]]
}[number];
function oneOf(...[variable, opts]: ValidArgs<[IType1, IType2]>): boolean {
return (opts as readonly (IType1 | IType2)[]).includes(variable);
}
oneOf('text4', ['text3', 'text5']); // okay
oneOf('text4', ['text3', 'text2']); // error
這和以前一樣;ValidArgs<[IType1, IType2]>評估為[variable: IType1, opts: IType1[]] | [variable: IType2, opts: IType2[]]。它的作業原理是獲取輸入元組型別[IType1, IType2]并對其進行映射以形成一個新型別[[variable: IType1, opts: IType1[]], [variable: IType2, opts: IType2[]]],然后我們立即使用索引對其進行number索引以獲得該元組元素的聯合;即[variable: IType1, opts: IType1[]] | [variable: IType2, opts: IType2[]].
您可以看到如何ValidArgs更容易擴大規模:
type Test = ValidArgs<[0, 1, 2, 3, 4, 5]>;
// type Test = [variable: 0, opts: 0[]] | [variable: 1, opts: 1[]] | [variable: 2, opts: 2[]] |
// [variable: 3, opts: 3[]] | [variable: 4, opts: 4[]] | [variable: 5, opts: 5[]]
無論如何,根據您的用例,所有這些版本都應該在呼叫端運行得相當好。
Playground 代碼鏈接
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/416612.html
標籤:
