Typescript:声明元组索引的类型?

3

给定一个 TypeScript Tuple,例如:

const arr = [1, 2] as const;

我们在索引时进行静态类型检查:
console.log(arr[1]);

是好的,但是

console.log(arr[2]);

错误信息:

Tuple类型'readonly [1, 2]'的长度为'2',在索引'2'处没有元素。ts (2493)

这很好。


我想声明一个常量,使其类型为该元组的索引类型,这样可以将符合相同约束条件(0 | 1)的内容分配给该常量。

我尝试了以下代码:

const index: keyof typeof arr = 2 as const;
console.log(arr[index]);

但 TypeScript 没有显示任何错误。我怀疑 index 是一个过于宽泛的 number 类型。


2
这种方法是否符合您的需求?如果是,我可以撰写一篇解释性答案;如果不是,那么我缺少什么? - jcalz
@jcalz 是的,看起来差不多正确。 - Ivan Rubinson
2个回答

6

出于某种原因,元组索引是数字字符串文字类型,如"0""1",而不是相应的数字文字类型,如01。还有一个number索引签名,所以keyof ["a", "b"]将给你number | "0" | "1" | ...。这意味着只需使用keyof就可以分配任何数字。

如果要计算数字文字,则可以在TypeScript 4.8及以上版本中使用模板文字类型来实现:

type TupleIndices<T extends readonly any[]> =
    Extract<keyof T, `${number}`> extends `${infer N extends number}` ? N : never;

首先,我提取数值字符串文字类型,通过过滤`${number}`来获得完整键集,即可以解析为数字的所有字符串。这样可以消除number本身(它不是一个字符串),以及其他数组成员,如"length""slice"。因此,我们得到了"0" | "1" | "2" | ...。然后,我使用TypeScript 4.8中引入的改进的infer类型将这些字符串文字转换为数字文字。

这样就得到了以下结果:

const arr = [1, 2] as const;

type ArrIndices = TupleIndices<typeof arr>;
// type ArrIndices = 0 | 1

type Also = TupleIndices<[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]>;
// type Also = 0 | 1 | 2 | 9 | 3 | 4 | 5 | 6 | 7 | 8

代码的游乐场链接


1

这是一种基于尾递归定义数字范围的小技巧:

type Enumerate<
    N extends number,
    Acc extends number[] = [],
> = Acc['length'] extends N
    ? Acc[number]
    : Enumerate<N, [...Acc, Acc['length']]>;
type Range<F extends number, T extends number> = Exclude<
    Enumerate<T>,
    Enumerate<F>
>;

const index: Range<0, typeof arr['length']> = 2 as const;
// Type '2' is not assignable to type '0 | 1'. ts(2322)

是的,我也在尝试提出那种方法。然后jcalz发表了他们的评论,我只是想说"嗯,我是说,如果你想以简单的方式解决..." :-) - T.J. Crowder
1
如果您使用的是TS4.7或更低版本,那么这几乎是唯一的方法,因为只有TS4.8引入了将字符串字面类型转换为数字字面类型的方式。 (https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#improved-inference-for-infer-types-in-template-string-types) - jcalz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接