我如何通过只提到所提供的数组是string[]子类型而不显式地提到type1type2,例如通过arr: type1 | type2来一般性地键入下面的函数processArray

我们的目标是不让底部的所有四个函数调用示例都出现TS错误.有关返回错误的两个调用,请参阅此playground.

调用此函数的原因是,该函数将在大约有十个联合类型的应用程序中使用,每个联合类型都将函数将处理的类型作为其子类型(这些类型是从OpenAPI规范生成的,因此future 当规范更改时可能会有其他类型).我希望避免为所有这些类型创建一个新的联合类型,或者将它们指定为函数参数可能接受的类型.理想情况下,我只想指定函数所使用的子类型.

function processArray<T extends string[]>(arr: T): void {
    console.log(arr);
}

const sArr = ["a", "b", "c"];
const nArr = [1, 2, 3];
const bArr = [true, false];

const arr1 = Math.random() < 0.5 ? sArr : nArr
processArray(arr1); // error, but shouldn't be

const arr2 = Math.random() < 0.5 ? sArr : bArr
processArray(arr2); // error, but shouldn't be

const arr3 = Math.random() < 0.5 ? nArr : bArr;
processArray(arr3); // error as intended, since arr3 cannot possibly be a string array

推荐答案

Typescript 的generic constraints只起到了所谓的upper-bound的作用.如果你有T extends string[],那么它要求Tsubtype ofassignable to string[].您想要的更像是lower-bound,因为您希望string[]可以分配给T.也就是说,您希望它是可以accept个字符串数组的类型,而不是可以provide个字符串数组的类型.microsoft/TypeScript#14520有一个功能请求要求这样做,但到目前为止,它不是语言的一部分.

实际上,听起来您想要一个略有不同的约束.您希望T是某种数组类型,因此是T extends readonly unknown[],并且希望该数组的元素类型是possibly是某种类型的string.因此,与其说元素类型从上到下由string限定,更像是您想说类型和string之间有some overlap.因此,如果数组中的一个元素是stringstring | 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

Typescript相关问答推荐

如何将绑定到实例的类方法复制到类型脚本中的普通对象?

了解TS类型参数列表中的分配

TypeScript Page Routing在单击时不呈现子页面(React Router v6)

如何在排版中正确键入中间件链和控制器链

webpack错误:模块找不到.当使用我的其他库时,

异步动态生成Playwright测试,显示未找到测试

rxjs forkJoin在错误后不会停止后续请求

material 表不渲染任何数据

是否使用非显式名称隔离在对象属性上声明的接口的内部类型?

PrimeNG日历需要找到覆盖默认Enter键行为的方法

参数属性类型定义的REACT-RUTER-DOM中的加载器属性错误

TYPE FOR ARRAY,其中每个泛型元素是单独的键类型对,然后在该数组上循环

在Cypress中,是否可以在不标识错误的情况下对元素调用.Click()方法?

TypeScrip:如何缩小具有联合类型属性的对象

如何使用警告实现`RecursiveReadonly<;T>;`,如果`T`是原语,则返回`T`而不是`RecursiveReadonly<;T>;`

为什么看起来相似的代码在打字时会产生不同的错误?

参数未映射泛型返回类型

TS2532:对象可能未定义

在Typescript 中,是否有一种方法来定义一个类型,其中该类型是字符串子集的所有可能组合?

使用 TypeScript 在 SolidJS 中绘制 D3 力图