我有一个简单的函式,它检查是否variable
包含在阵列中opts
type IType1 = 'text1' | 'text2';
type IType2 = 'text3' | 'text4' | 'text5';
const foo: IType2 = 'text4';
function oneOf(variable, opts) {
return opts.includes(variable);
}
我想要的是使opts
andvariable
相互依赖,所以如果我呼叫该函式:
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'
IType1
IType2
'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 代码链接
0 评论