纠正和统一解构列表的类型

Correct and unify type of destructured list

我想操作值数组的数组,但在解构列表时找不到正确的类型。
我数组的值不是 . I am neither looking into . I am trying to destructure arrays, .


我有以下 ts 的非编译代码片段:

const bar = (n: number, s: string, p: string): string => n === 1 ? s : p;

const f = (args: [number, string, string]): string => bar(...args)

const r = [
  [1, 'a', 'b'],
  [2, 'c', 'd'],
].map(f);

抛出错误:

Argument of type '(args: [number, string, string]) => string' is not assignable to parameter of type '(value: (string | number)[], index: number, array: (string | number)[][]) => string'.
  Types of parameters 'args' and 'value' are incompatible.
    Type '(string | number)[]' is not assignable to type '[number, string, string]'.
      Target requires 3 element(s) but source may have fewer.

与 Ts v4.3.5 offiial editor。生成的 js 确实适用于 node

➜  ~ node 
Welcome to Node.js v14.17.0.
Type ".help" for more information.
> const bar = (n, s, p) => n === 1 ? s : p;
undefined
> const f = (args) => bar(...args);
undefined
> const r = [
...     [1, 'a', 'b'],
...     [2, 'c', 'd'],
... ].map(f);
undefined
> r
[ 'a', 'd' ]

是否可以概括这一点?

背景

不幸的是,问题是 TypeScript 不会为您期望它推断的输入数据推断元组类型。

考虑以下类型:

const t = [1, 'a', 'b']
// type of t: (string | number)[]

const t2 = [1, 'a', 'b'] as const
// type of t2: readonly [1, 'a', 'b']

第一个赋值将推断给定元素类型的联合数组。在第二个赋值中,我们使用 as const 加法来强制编译器推断最具体的类型。如您所见,它现在实际上推断文字类型(例如 'a' 而不是字符串)。

这可能也不总是我们想要的。您当然可以将元素转换为正确的类型。然而,我自己经常使用一个辅助函数,它除了推断我最常需要的类型外什么都不做:

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

const t3 = tuple(1, 'a', 'b')
// type of t3: [number, string, string]

解决方案 1

让我们将这些知识应用到您的示例中:

const input = [
  [1, 'a', 'b'],
  [2, 'c', 'd'],
]
// type of input: (string | number)[][]

const input2 = [
  tuple(1, 'a', 'b'),
  tuple(2, 'c', 'd')
]
// type of input2: [number, string, string][]

最后一个类型正是您输入类型 f 所需要的。

结合所有内容,我们得到:

const bar = (n: number, s: string, p: string): string => n === 1 ? s : p;

const f = (args: [number, string, string]): string => bar(...args)

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

const r = [
  tuple(1, 'a', 'b'),
  tuple(2, 'c', 'd'),
].map(f);
// type of r: string[]

解决方案 2

如果您不想细化输入类型,您还可以为 .map 函数创建一个帮助程序来推断正确的类型:

const bar = (n: number, s: string, p: string): string => (n === 1 ? s : p);

const f = (args: [number, string, string]): string => bar(...args);

function mapValues<T, R>(values: T[], f: (value: T) => R): R[] {
  return values.map(f);
}

const r = mapValues(
  [
    [1, "a", "b"],
    [2, "c", "d"]
  ],
  f
);

结果应该是一样的。

解决方案 3

如果您真的不喜欢或不能使用辅助函数,我能想到的最简单的解决方案是使用 as const。您需要稍微更改 f 的类型,因为数组将变为只读:

const bar = (n: number, s: string, p: string): string => (n === 1 ? s : p);

const f = (args: readonly [number, string, string]): string => bar(...args);

const r = ([
  [1, "a", "b"],
  [2, "c", "d"] 
] as const).map(f);

f 函数期望它的第一个参数是一个元组:[number, string, string].

但是,数组 r 中的项目的类型为 (string | number)[]。所以一个字符串或数字数组。

因此,您需要告诉 TypeScript 数组项是元组。例如:

const r: [number, string, string][] = [
  [1, 'a', 'b'],
  [2, 'c', 'd'],
];

const result = r.map(f);