考虑一下代码

/* eslint-disable */
type Primitive = boolean | null | number | string;

export type DataAddress<T> = T extends Primitive ? [T] : ObjectAddress<T>;

export type ObjectAddress<T> = {
  [K in keyof T]: [K, ...DataAddress<T[K]>];
}[keyof T];

type X = { x: boolean[] }

function f(...args: DataAddress<X>) {

}


f('x1') 
/* TS2345: Argument of type ["x1"] is not assignable to parameter of type
 ["x", ...any[]] | ["x", number, false] | ["x", number, true]
 */

问题是["x", ...any[]]从哪里来,以及如何修复代码,使这里的类型仅为["x", number, false] | ["x", number, true]

推荐答案

你遇到的问题是

type ObjectAddress<T> = {
  [K in keyof T]: [K, ...DataAddress<T[K]>];
}[keyof T];

是由[keyof T]号引起的,在那里结束了.您的ObjectAddress<T>是众所周知的distributive object type(在microsoft/TypeScript#47109中被创造出来),这是当您编写mapped type,然后立即index into它将所有的键,产生union的属性值类型时得到的.

或者至少,这是它应该做的,对于"常规"对象类型,它的工作方式就像广告中所说的那样.但当Tarray typetuple type时,事情就会出错.映射类型{[K in keyof T]: ⋯}仅迭代类数字键和produces another array or tuple type.But,末尾的keyof T只是not个类数字键.它是数组类型T的所有键,包括"length""push"等.因此,对于每个数字K,你不会只得到[K, ...DataAddress<T[K]>]的并集,你还会得到所有其他成员的并集,比如number代表length,push代表某种函数类型,et Cetra.真是一团糟.

因此,一种解决方法是判断T是否类似数组,如果是,则只使用number作为索引,而不是keyof T.也许是这样的:

type ObjectAddress<T> = {
  [K in keyof T]: [K, ...DataAddress<T[K]>];
}[(T extends readonly any[] ? number : unknown) & keyof T];

在这里,对于类似数组的对象,conditional type的计算结果为number,对于非数组的对象,conditional type的计算结果为unknown.当我们用keyof T表示intersect时,当T是类似数组的时候,它的计算结果是number & keyof T(很可能是number),或者当T是非数组时,它的计算结果是keyof T.

一旦我们做出了这样的改变,你的例子就会像预期的那样运行:

type X = { x: boolean[] }
type Z = DataAddress<X>
//   ^? type Z = ["x", number, false] | ["x", number, true]

Playground link to code

Typescript相关问答推荐

在Switch陈述中输入保护类

TypScript中的算法运算式

TS 2339:属性切片不存在于类型DeliverableSignal产品[]上>'

更新:Typescript实用程序函数合并对象键路径

泛型和数组:为什么我最终使用了`泛型T []`而不是`泛型T []`?<><>

如果请求是流,如何等待所有响应块(Axios)

单击并移除for循环中的一项也会影响另一项

如何正确地对类型脚本泛型进行限制

使用泛型keyof索引类型以在if-condition内类型推断

获取函数中具有动态泛型的函数的参数

返回具有递归属性的泛型类型的泛型函数

如何在Reaction Query Builder中添加其他字段?

TypeScrip:来自先前参数的参数中的计算(computed)属性名称

如何使用模板继承从子组件填充基础组件的画布

在单独的组件中定义React Router路由

埃斯林特警告危险使用&as";

使用泛型以自引用方式静态键入记录的键

可以将JS文件放在tsconfig';s includes/files属性?或者,我如何让tsc判断TS项目中的JS文件?

如何正确键入泛型相对记录值类型?

在 next.js 13.4 中为react-email-editor使用forwardRef和next/dynamic的正确方法