我可以声明一种数组文字吗?

can I declare a type of an array literal?

在我的打字稿代码中,我有这个片段:

pairs.forEach(([n, s]) => {
  console.log(n * 3, s.toUpperCase());
});

我想内联“pairs”变量。我知道我可以这样做:

([[3, "three"], [5, "five"]] as [number, string][]).forEach(([n, s]) => {
  console.log(n * 3, s.toUpperCase());
});

但我不喜欢它,因为现在我的代码变得更糟了。在内联之前,编译器正在验证数组的每个元素都是一对数字和字符串 - 因此,例如,此代码无法编译:

const pairs: [number, string][] = [[3, "three"], [5, "five"], ["seven", 7]];
pairs.forEach(([n, s]) => {
  console.log(n * 3, s.toUpperCase());
});

但是内联后,我的数组没有声明类型。相反,我使用类型断言,它告诉编译器忽略它认为的类型是什么,并相信我的话。所以现在编译器不会验证数组的每个元素都是一对数字和字符串。因此,例如,这段代码可以编译,但只会在运行时失败:

([[3, "three"], [5, "five"], ["seven", 7]] as [number, string][]).forEach(([n, s]) => {
  console.log(n * 3, s.toUpperCase());
});

我能否以某种方式同时拥有这两个东西 - 都内联这个数组并告诉编译器它的类型是什么?我能以某种方式声明数组文字的类型吗?

我认为你不能真正自动完成。

let pairs = [[3, "three"], [5, "five"]] 中,pairs 的类型是 auto-detected 作为 (string | number)[][] 而不是 ([number,string][] )
这就是为什么在内联之后,它的行为不像你希望的那样,它只会使用它推断的 (string | number)[][]

似乎 auto-detect 一有机会就尝试检测 variable-length 数组而不是元组。
因此,let x = [3,4] 被检测为 number[] 而不是 [number,number]
let pair = [3,'three'] 被检测为 (string|number)[] 而不是 [number,string]

您要找的魔法词是as const:

([[1, 'foo'], [2, 'bar'], [3, 'baz']] as const)
  .forEach(([n, s]) => console.log(n * 3, s.toLocaleUpperCase()));

请注意,as const 比您在此处寻找的更进一步:此数组的类型是

readonly [readonly [1, "foo"], readonly [2, "bar"], readonly [3, "baz"]]

并且 n 的类型是 1 | 2 | 3 而不是 number 并且 s 的类型是 "foo" | "bar" | "baz" 而不是 stringreadonly 标记可能特别烦人,因为 readonly T[] 不能分配给 T[].

我经常使用as const,因为它真的很强大,但我也经常在创建这样的元组时使用实用函数,但是不要想要将事物变成 readonly 个特定文字数组:

function tuple<T extends unknown[]>(...args: T): T { return args; }

(请注意,作为一个实际函数,这确实会引入运行时开销——as const 不会。但是它非常小。)

有了这个,我们就可以使用

tuple(tuple(1, 'foo'), tuple(2, 'bar'), tuple(3, 'baz'))
  .forEach(([n, s]) => console.log(n * 3, s.toLocaleUpperCase()));

现在我们的数组是 [[number, string], [number, string], [number, string]]n 就是 numbers 就是 string

See all of this in the TS Playground