在 Typescript 中,为数组定义一个类型,其中第一个元素比其他元素更具体
Posted
技术标签:
【中文标题】在 Typescript 中,为数组定义一个类型,其中第一个元素比其他元素更具体【英文标题】:In Typescript, define a type for an array where the first element is more specific than the rest 【发布时间】:2017-11-08 04:24:57 【问题描述】:我想为一个数组定义一个类型,它的第一个元素是特定类型(例如函数),其余元素是空类型。例如:
type FAs = [Function, , , , ...]; // pseudo code
这样的事情可能吗?
目的是提供这样的单参数函数:
const myCaller = ([fun, ...args]: FAs) => fun.apply(args);
另一种方法是对myCaller
使用两个参数,如下所示:
const myCaller = (fun: Function, args: any[]) => fun.apply(args);
但出于美学原因,我更喜欢使用单个参数。我还想知道类型系统是否支持可以说是任意长度的元组。也许出于我不理解的计算机科学原因,这种事情是不可取的。
【问题讨论】:
有些相关:github.com/Microsoft/TypeScript/issues/212 【参考方案1】:我很确定这是 Typescript 2.3 中你能做到的最好的。例如,你可以在 lodash 中看到这样的类型。
interface IMyCaller
<R>([fn]: [() => R]): R;
<R,A>([fn, a]: [(a: A) => R, A]): R;
<R,A,B>([fn, a, b]: [(a: A, b: B) => R, A, B]): R;
<R,A,B,C>([fn, a, b, c]: [(a: A, b: B, c: C) => R, A, B, C]): R;
// keep adding these until you get tired
const myCaller: IMyCaller = ([fun, ...args]) => fun.apply(args);
【讨论】:
这也是我的想法,但 Typescript 对未明确定义的元素使用联合类型非常有帮助。请参阅接受的答案以获得出色的描述,以及一个重要的警告。 你是对的,如果你不需要严格的输入,它会很有帮助。我想我被带走了,并没有非常关注你的问题:)【参考方案2】:如果你定义
type FAs = [Function, ];
那么FAs
类型的值将需要Function
类型的第一个元素、 类型的第二个元素以及
Function |
类型的后续元素。这就是 TypeScript 文字数组类型的工作方式。来自TS docs:
当访问已知索引集之外的元素时,将使用联合类型:
这应该可以满足您的所有需求除了,因为您可以将Function
-typed 值作为数组的第三个元素等传递。但实际上无论如何都是这样,因为Function
与 兼容。
没有办法解决这个问题。在 TS 中没有办法定义一个数组类型,其中前 n 个元素属于某些特定类型,并且存在任意数量的其他特定类型的剩余元素。
我还想知道类型系统是否支持可以说是任意长度的元组。
实际上,类型系统仅支持任意长度的元组。如果你说
type Tuple = [number, number];
此类型与任何长度为 2或更大且包含数字的数组兼容。如果你说
type Tuple = [string, number];
此类型与长度为 2或更长的任何数组兼容,该数组的第一个元素为字符串,第二个元素为数字,第三个元素为字符串或数字,依此类推。我不会将这种行为的原因称为“基于计算机科学”;更重要的是 TS 检查什么是可行的。
另一种方法
interface Arglist
[index: number]: object;
0: Function;
const a1: Arglist = [func];
const a2: Arglist = [22]; // fails
const a3: Arglist = [func, "foo"]; // fails
const a4: Arglist = [func, obj];
const a5: Arglist = [func, obj, obj];
【讨论】:
我不知道 Typescript 会为已知索引集之外的元素使用联合类型,尽管我已经多次阅读该文档页面。感谢您的清晰解释和文档链接。 不幸的是,这对我使用 Typescript 3.0.3 不起作用。当我声明一个使用这种类型的变量时,它抱怨长度不匹配。至于替代方法,我想使用地图、过滤器等,因此不得不修改接口以包含这些定义。 @FTWinstone 上面的type
声明具有任意长度,需要在结果数组中包含 2 个或更多元素。此外,第二个之后的任何元素的类型将不限于string|number
而不仅仅是number
。要使类型定义更接近上面的接口,您可以使用type myType = [string, ...number];
,这意味着第一个元素必须是字符串,而任何其他元素,如果有,必须是数字。
看起来这个答案现在已经过时了?这个问题有newer 的答案,including TypeScript 4.0。【参考方案3】:
在当前版本的 Typescript 中,这可以使用数组扩展:
type FAs = [Function, ...Array<>]
它支持从1到n的任意长度(第一个元素是必需的)。
【讨论】:
【参考方案4】:我还想知道类型系统是否支持可以说是任意长度的元组。
自 TS 4.0 起,您可以使用variadic tuple types。
例如,以类型安全的方式断言 [func, ...<proper func args>]
:
type FAs<A extends unknown[], R> = [(...args: A) => R, ...A]
const myCaller = <A extends unknown[], R>([fn, ...args]: FAs<A, R>) =>
fn.apply(null, args)
例子:
const fn1 = (a1: string, a2: number) => true
const r1 = myCaller([fn1, "foo", 42]) // OK, r1 has type `boolean`
const r2 = myCaller([fn1, "foo", "bar"]) // error, `bar` has wrong type
const r3 = myCaller([fn1, "foo"]) // error, not enough arguments
Playground
【讨论】:
【参考方案5】:type First<T> = T extends [infer U, ...any[]] ? U : any;
type F = First<[number, boolean, string]> // number
【讨论】:
能有反转版type Rest<T>
吗? type F = Rest<[number, boolean, string]> // [boolean, string]
【参考方案6】:
假设您想要一个至少有两个元素的Array
,第一个是A
类型,然后是B
类型。
更通用的方法是定义交叉类型如下:
type A = string
type B = number
// ensures that there are at least two elements
interface Foo
0: A;
1: B;
// ensures that the first element is of type A and any after that of type B
type Bar = [A, ...B[]]
type MyArrayType = Foo & Bar
let a: MyArrayType
a = ['hello world', 1] // works
a = ['hello world', 1, 2] // works
a = ['hello world'] // fails
a = ['hello', 'world'] // fails
请注意,即使 A 不是 B 的子集,这也有效。接受的答案需要这样做。
我意识到原始问题不需要此约束。但是,由于我遇到了具有这些限制的线程,它可能会对其他人有所帮助。
【讨论】:
以上是关于在 Typescript 中,为数组定义一个类型,其中第一个元素比其他元素更具体的主要内容,如果未能解决你的问题,请参考以下文章