Typescript 版本:5.1.6

以下是对我的问题的最小重建:

我有一个函数f,它将接受C的数组,其中

declare class C<a = unknown, b = unknown> {
  getA(): a;
  getB(): b;
};

C的第一个泛型,也就是A,将是一个对象.

我希望explicitlyC的数组作为f的元组

declare function f<T extends any[]>(arr: [...T]): any;

declare const a: C<{ name: string }, []>;
declare const b: C<{ name: string }, []>;

f([a, b])

f上徘徊,我们可以准确地看到我们想要的东西.

function f<[C<{
    name: string;
}, []>, C<{
    name: string;
}, []>]>(arr: [C<{
    name: string;
}, []>, C<{
    name: string;
}, []>]): any

当我想用另一个类型包装数组类型时,问题出现了,该类型将判断T上是否有重复的键.

当我做这样的事情时:

type ExtractObject<I> = I extends C<infer P> ? P : never;
type ExtractObjectFromList<I> = {
  [P in keyof I]: ExtractObject<I[P]>;
};


declare function f<T extends any[]>(arr: HasDuplicateKey<ExtractObjectFromList<[...T]>> extends true ? "Err" : T): any;

declare const a: C<{ name: string }, []>;
declare const b: C<{ name: string }, []>;

f([a, b])

declare function f<T extends any[]>(arr: [...T] extends infer P ? HasDuplicateKey<ExtractObjectFromList<P>> extends true ? "err" : P: never): any;

declare const a: C<{ name: string }, []>;
declare const b: C<{ name: string }, []>;

f([a, b])

it is not w或king c或rectly since it is losing the tuple type and it is converted to (C<{ name: string }, []>[])[]

我想知道是否有解决这个问题的方法,而不是在传递数组时使用as const,这是库的一部分,我想要向后压缩.

So the idea is to highlight to display an err或 message when the keys of the first generic in C has a collision and also ability to do some m或e checks on top of that

还有,这是HasDuplicateKey型的

exp或t type HasDuplicateKey<T> = T extends readonly [infer First, ...infer Rest]
  ? Rest extends readonly [infer Next, ...infer Others]
    ? keyof First extends keyof Next
      ? true
      : HasDuplicateKey<readonly [First, ...Others]>
    : false
  : false;

推荐答案

您希望模拟一个循环泛型constraint function f<T extends F<T>>(arg: T) {},其中F<T>是一个"验证"类型函数,其中T extends F<T>本质上等于某个超类型T,当且仅当T是"有效的"时.有时您实际上可以直接编写这样的约束,但通常编译器会抱怨它是循环的.

有多种方法可以模拟这样的约束,但是很难找到一种方法不 destruct 编译器从传递给arg的参数推断T的能力.它是易碎的.

有时你可以写function f<T>(arg: F<T>) {},它就能奏效.有时这不管用,但function f<T>(arg: T & F<T>) {}管用.有时that不起作用,但function f<T>(arg: T extends F<T> ? T : F<T>) {}起作用.有时这些都不起作用;看起来就像T应该是tuple type,而你使用variadic tuple type提示function f<T extends any[]>(arg: [...F<T>]) {},那么它通常就是……休息一下.

在这种情况下,您可以try 根据作用于每个元组元素individually的验证类型函数G<T>来重写验证类型函数F<T>,因此F<T>将是表单{[I in keyof T]: G<T[I]>}homomorphic mapped type(见What does "homomorphic mapped type" mean?).这似乎或多或少保留了推理行为,但需要您重构判断.


对于如下所示的示例,我可能会这样写它:

declare function f<T extends C<any, any>[]>(
  arr: [...DeDuplicateKeys<T>]
): T;

type DeDuplicateKeys<T extends C<any, any>[]> =
  { [I in keyof T]: T[I] &
    C<Record<RepeatsAtIndex<Keys<T>, I>, never>, any>
  }

type RepeatsAtIndex<T, I> =
  T[Extract<keyof T, I>] & T[Exclude<keyof T, I>];

type Keys<T extends C<any, any>[]> =
  { [I in `${number}` & keyof T]:
    T[I] extends C<infer A, any> ? keyof A : never
  };

因此,DeDuplicateKeys<T>是我们的同态映射类型,它将判断T[I] & C<Record<RepeatsAtIndex<Keys<T>, I>, never>, any>应用于索引I处的T的每个元组元素.

其工作方式是获取T并将其映射到一个新的类似元组的类型,该类型仅包含C<A, B>A类型参数的键;这就是Keys<T>所做的(它不是真正的元组,但具有类似数字的索引"0""1"等).然后RepeatsAtIndex<Keys<T>, I>将只给你那些出现在元组which also的第I个元素中的键,出现在其他元素中.人们希望这将是never个.然后Record<RepeatsAtIndex<Keys<T>, I>, never>是具有所有这些重复键的对象类型和值类型never.如果有任何重复的关键点,这将产生不可能的类型,如{name: never}.否则,它将是允许的空对象类型{}.到intersecting乘以T[I],如果它是好的,我们将只得到T[I],如果它不好,我们将得到一些不可能的东西.

坦率地说,它很复杂,但您的约束也很复杂,因为它作用于元组元素内泛型类型的一个类型参数.


不管怎样,让我们来测试一下:

declare const a: C<{ name: string }, []>;
declare const b: C<{ name: string }, []>;
declare const c: C<{ age: number }>;
f([a, c]); // okay
// function f<[C<{ name: string; }, []>, C<{ age: number; }, unknown>]>(⋯): ⋯;
f([b, c]); // okay
// function f<[C<{ name: string; }, []>, C<{ age: number; }, unknown>]>(⋯): ⋯;
f([a, b, c]); // error
// ~  ~ <-- types of property "name" are incompatible
// function f<[
//   C<{ name: string }, []>, C<{ name: string }, []>, C<{ age: number }, unknown>
// ]>(⋯): ⋯;

看起来像是你想要的.在每种情况下,T类型参数都被推断为正确的元组类型,并且接受有效的组合,而拒绝无效的组合.


请注意,这里可能没有考虑到边缘情况,但我在这里采取的一般方法仍然是try 从同态映射类型中编写验证类型函数.

Playground link to code

Typescript相关问答推荐

即使子网是公共的,AWS CDK EventBridge ECS任务也无法分配公共IP地址

在将对象从一个对象转换成另一个对象时,可以缩小对象的键吗?

如何使用泛型类型访问对象

类型脚本类型字段组合

在Reaction-Native-ReAnimated中不存在Animated.Value

如何在react组件中使用doctype和html标签?

如果数组使用Reaction-Hook-Form更改,则重新呈现表单

在类型脚本中创建泛型类型以动态追加属性后缀

防止重复使用 Select 器重新渲染

从子类集合实例化类,同时保持对Typescript中静态成员的访问

将带有样式的Reaction组件导入到Pages文件夹时不起作用

如何键入派生类的实例方法,以便它调用另一个实例方法?

Typescript,如何在通过属性存在过滤后重新分配类型?

如何为带有参数的函数类型指定类型保护?

如何调整项目数组,但允许权重影响顺序

基于区分的联合键的回调参数缩小

从联合提取可调用密钥时,未知类型没有调用签名

传入类型参数<;T>;变容函数

Typescript 子类继承的方法允许参数中未定义的键

元素隐式具有any类型,因为string类型的表达式不能用于索引类型{Categories: Element;管理员:元素; }'