Typescript 的generic constraints只起到了所谓的upper-bound的作用.如果你有T extends string[]
,那么它要求T
是subtype of或assignable to string[]
.您想要的更像是lower-bound,因为您希望string[]
可以分配给T
.也就是说,您希望它是可以accept个字符串数组的类型,而不是可以provide个字符串数组的类型.microsoft/TypeScript#14520有一个功能请求要求这样做,但到目前为止,它不是语言的一部分.
实际上,听起来您想要一个略有不同的约束.您希望T
是某种数组类型,因此是T extends readonly unknown[]
,并且希望该数组的元素类型是possibly是某种类型的string
.因此,与其说元素类型从上到下由string
限定,更像是您想说类型和string
之间有some overlap.因此,如果数组中的一个元素是string
、string | boolean
、"a" | "b" | "c"
或"a" | number
,那就没有问题了.您只想在根本没有重叠的情况下拒绝它,比如数组元素类型是number | boolean
.
所以我们需要一种在约束中表达这一点的方法.幸运的是,我们可以这样做:
function processArray<T extends readonly unknown[] &
(string & T[number] extends never ? never : unknown)
>(arr: T): void {
console.log(arr);
}
这是一个碰巧被接受的自引用约束(也称为F-bounded)(有时TS会将它们作为循环拒绝).它说T
必须赋值给readonly unknown[]
,and(使用[交集])(https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types),我们需要它赋值给conditional type,string & T[number] extends never ? never : unknown
.类型string & T[number]
是string
和元素类型T
之间的交集或overlap(如果T
是一个数组,而您index into it的键是类型number
,那么您将获得元素类型,因此是T[number]
).如果重叠类型为空,则为the impossible never
type,且条件类型计算为不接受never
类型.如果重叠类型不为空,则条件类型求值为the accept-everything unknown
type.
因此,换句话说,如果T
‘S元素类型可能包含string
类型,则约束的计算结果为T extends readonly unknown[]
,它将接受array...但如果不能,则约束的计算结果为T extends never
,整个过程将失败.
让我们来测试一下:
const sArr = ["a", "b", "c"];
const nArr = [1, 2, 3];
const bArr = [true, false];
const arr1 = Math.random() < 0.5 ? sArr : nArr
processArray(arr1); // okay
const arr2 = Math.random() < 0.5 ? sArr : bArr
processArray(arr2); // okay
const arr3 = Math.random() < 0.5 ? nArr : bArr;
processArray(arr3); // error
processArray(["a", "b"] as const); // okay
processArray(["a", 1] as const); // okay
processArray([1, 2] as const); // error
看上go 不错.只有不接受任何类型的string
的数组才会被拒绝.
Playground link to code个