You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript鸭子类型是否排除类构造函数及PickFromConstructor疑问

问题解析与解答

首先假设你的代码实现大致如下(符合问题描述的场景):

type PickFromConstructor<T extends new (...args: any[]) => any, K extends keyof T> = 
  Pick<T, K> & new (...args: ConstructorParameters<T>) => InstanceType<T>;

class A {
  static optionalProp?: string;
  static requiredProp: number = 1;
  instanceProp: boolean = true;

  constructor(public name: string) {}
}

type PickedA = PickFromConstructor<A, 'requiredProp'>;

// 底部赋值示例
const picked: PickedA = A;

// 六个Extends测试示例
type Test1 = A extends PickedA ? true : false;
type Test2 = PickedA extends A ? true : false;
type Test3 = InstanceType<A> extends InstanceType<PickedA> ? true : false;
type Test4 = InstanceType<PickedA> extends InstanceType<A> ? true : false;
type Test5 = typeof A extends PickedA ? true : false;
type Test6 = PickedA extends typeof A ? true : false;

1. 类型A相较于PickedA缺失了哪些属性?

需要明确:A是类的类型(同时包含构造函数签名和所有静态属性),PickedA是通过PickFromConstructor生成的类型。二者的实际差异是:

  • PickedA仅保留了A中指定的静态属性requiredProp,以及A的构造函数签名;
  • 因此并非A缺失属性,而是PickedA缺失了A的静态可选属性optionalProp。

2. 当构造函数类型的InstanceType包含额外属性时,差异产生的原因是什么?

TypeScript的构造函数类型(如new () => T)是声明式类型,其InstanceType<T>是显式指定的实例类型;而实际类的实例类型是推导式结构类型。当二者不一致时:

  • 若构造函数类型的InstanceType包含实际类实例没有的属性,PickFromConstructor会基于这个声明的InstanceType约束返回的构造函数实例类型;
  • 但实际类的实例并不具备这些额外属性,这就导致类型层面的约束和运行时实际结构的差异——TypeScript会按声明类型做检查,但运行时访问这些额外属性会得到undefined或报错。

本质是TypeScript结构类型系统中,「声明类型」与「实际推导类型」不匹配导致的差异。

3. 文件底部的赋值为何没有报错?

const picked: PickedA = A;不报错的核心是TypeScript的结构类型兼容性规则:

  • PickedA的结构是「包含requiredProp静态属性 + 与A一致的构造函数签名」;
  • A的类型完全满足这个结构(A既有requiredProp,又有对应的构造函数);
  • 即A的类型是PickedA的超类型,超类型可以赋值给子类型,因此不会触发错误。

4. 为何六个...Extends...类型的结果与预期不符?

先明确上述示例中六个测试的实际结果:

  • Test1 = true(A extends PickedA):A包含PickedA的所有属性,满足子类型约束;
  • Test2 = false(PickedA extends A):PickedA缺少A的optionalProp静态属性,不满足;
  • Test3 = true(InstanceType<A> extends InstanceType<PickedA>):二者实例类型完全一致;
  • Test4 = true(同上,双向兼容);
  • Test5 = true(typeof A就是A的类型,同Test1);
  • Test6 = false(同Test2)。

如果你的预期与实际结果不符,通常是两个原因:

  1. 混淆了超类型与子类型的判断逻辑:你可能误以为PickedA是A的子类型就应该PickedA extends A为true,但实际上子类型必须包含父类型的所有属性,PickedA缺少optionalProp,因此不满足;
  2. PickFromConstructor实现存在缺陷:比如错误地将K extends keyof InstanceType<T>(实例属性)而非keyof T(静态属性),或者构造函数签名的参数/实例类型处理错误,导致生成的PickedA结构与预期不符,进而影响extends的结果。

内容的提问来源于stack exchange,提问作者Lemon Lord

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 17:05:34