typescript 可以用任意类型示例化,

nsc4cvqm  于 2023-10-22  发布在  TypeScript
关注(0)|答案(1)|浏览(121)

我有几个命名空间,它们都包含一个同名的嵌套类,例如名为R。我想做一个通用函数作为工厂,它可以:示例化一个R的新示例,如果我只把命名空间作为参数传递的话,所以我得到了这里:

namespace A {
  export class R {
    public i = 0;
  }
}

namespace B {
  export class R {
    public s = '';
  }
}

type Input = typeof A | typeof B;
function Test<T extends Input>(v: T): InstanceType<T['R']> {
// Type 'A.R | B.R' is not assignable to type 'T["R"]'.
//  'T["R"]' could be instantiated with an arbitrary type which could be unrelated to 'A.R | B.R'.(2322)
  return new v.R();
}

const instance = Test(A);

我的目的是让Test函数自动推断并返回T['R']。然而,我刚刚得到了上面显示的错误。
为什么v.R()被推断为'A.R | B.R',而不是根据T缩小?
操场

5n0oy7gb

5n0oy7gb1#

TypeScript无法对依赖于generic类型参数的conditional types进行太多分析。一旦指定了类型参数,这些类型就可能变成任何类型,因此类型检查器大多数情况下就放弃了。它将这些类型视为 opaque,并且通常无法验证是否有任何特定的值可分配给它们。由于InstanceType<>实用程序类型是作为条件类型实现的,并且由于T是泛型类型参数,因此类型InstanceType<T["R"]>是这样一个不透明的类型,编译器将抱怨任何返回值尚未确切知道是InstanceType<T["R"]>return语句。
此外,当查看涉及new v.R()等泛型类型值的操作时,编译器通常会做出简化假设,即泛型类型参数可以扩展到其约束。所以在new v.R()中,v.R首先扩展为类型Input["R"],一个联合(参见microsoft/TypeScript#33181),然后new v.R()变成联合类型A.R | B.R,这与InstanceType<T["R"]>不同,赋值失败。
从本质上讲,编译器还不够聪明,无法遵循你的逻辑。
一种方法是通过重构来避免泛型条件类型。不要让T成为v参数的类型,而是让它成为返回的预期示例类型:

function Test<T extends InstanceType<Input["R"]>>(
  v: { R: new () => T }
): T {
  return new v.R(); // okay
}

因为v的类型是{R: new()=>T},所以编译器知道v.R的类型是new()=>T。这是一个单一的构造签名,没有参数,并产生一个T类型的值。因此new v.R()已知是T类型,并且该函数可以编译。
由于您显然不希望传入具有可构造R属性的任意对象,而是明确地只允许AB,因此我们
我把T限制为InstanceType<Input["R"]>。这计算为联合A.R | B.R。因此,该函数将只允许具有R属性的输入,该属性构造A.RB.R的示例。当然,这并不重要,因为该函数可以很好地工作在任何在R属性上具有零参数构造函数的对象上,所以您可能希望不约束T,这取决于用例。
好吧,让我们测试一下:

const instanceA = Test(A);
// const instanceA: A.R

const instanceB = Test(B);
// const instanceB: B.R

看起来不错。如果T受到约束,则以下内容将被拒绝:

// function Test<T extends InstanceType<Input["R"]>>(⋯
class X { x = 1 }
const instanceX = Test({ R: X }); // error!

但是如果T是无约束的,那么它被接受并产生一个适当类型的示例:

// function Test<T>(⋯
class X { x = 1 }
const instanceX = Test({ R: X }); // okay
// const instanceX: X

Playground链接到代码

相关问题