无法将类型分配给类型

时间:2019-06-06 17:00:10

标签: typescript typescript-generics

给予

export type Omit<T, K> = Pick<T, Exclude<keyof T, K>>

type AddDoSomething<T> = Omit<T, 'doSomething'> & {
  doSomething: () => void
}

class A<T> {
  public type: T
}

class B<TT> extends A<AddDoSomething<TT>> { }

// Just to check type match
type Test<T extends typeof A> = unknown

type C = Test<typeof B> // Error -> Type 'typeof B' does not satisfy the constraint 'typeof A'. Types of property 'type' are incompatible.

那是为什么?我试图将属性添加到某种类型,并且首先省略,我想确保“ doSomething”没有重叠类型

编辑-------

如果我更改了

的类型
  

AddDoSomething

type AddDoSomething<T> = T & {
  doSomething: () => void
}

那么一切都很好,有见识吗?我有什么办法可以不遗漏地覆盖“ doSomething”方法?

Link to Typescript Playground

1 个答案:

答案 0 :(得分:2)

至少在静态类型系统中,typeof B不会扩展typeof A是有意义的。这两种类型在运行时的行为很有可能完全相同,尤其是因为您实际上并没有在任何地方设置 type属性……但是这样的“运行时还可以,尽管看起来很糟糕”。 “类型系统”的行为对于编译器来说很难。如果您知道的更多,则可能需要使用类型断言。

让我们逐一解释为什么该错误有意义:

export type Omit<T, K> = Pick<T, Exclude<keyof T, K>>

type AddDoSomething<T> = Omit<T, "doSomething"> & {
    doSomething: () => void
}

class A<T> {
    public type!: T // forget about setting this
}

class B<T> extends A<AddDoSomething<T>> { }

因此,您已明确地使B<T>等效于A<AddDoSomething<T>>。但这意味着B<T> 等价于A<T>,正是因为T的某些值可能具有doSomething无参数功能。而且,如果构造函数B产生了B个实例,而构造函数A产生了A个实例,那么您就不能说B构造函数扩展了{ {1}}构造函数。您的A检查失败与此失败相同:

Test

如果可以很好地进行编译,则编译器会认为// if typeof B extends typeof A, then I can do this: const NotReallyA: typeof A = B; // error! // that error is the same as your Test. Why did it happen? NotReallyA的构造函数,而实际上却是A的构造函数。但是:

B

您会发现当您使用Legal {A}和{A1}时行为不同...因此,如果您使用const legitimateA = new A<{ x: number, doSomething: number }>(); legitimateA.type.x = 1; // okay, x is number legitimateA.type.doSomething = 2; // okay, doSomething is number const legitimateB = new B<{ x: number, doSomething: number }>(); legitimateB.type.x = 1; // okay, x is number legitimateB.type.doSomething = 2; // error! doSomething is ()=>void legitimateB.type.doSomething = () => console.log("ok"); // okay now 代替B(通过A),则会得到不同且不兼容的行为: / p>

NotReallyA

现在,由于您没有设置const illegitimateA = new NotReallyA<{ x: number, doSomething: number }>(); illegitimateA.type.x = 1; // okay, x is number illegitimateA.type.doSomething = 2; // okay at compiler time, but not good ,因此实际上两个构造函数在运行时实质上是相同的。如果您确实希望编译器将type视为B构造函数,则可以执行以下操作:

A

但是不能仅仅告诉编译器不要担心类型 // Are you sure this is okay? Then: const OkayIGuessA = B as typeof A; // assertion typeof B不兼容。


如果您愿意阻止typeof A拥有自己的T属性,或者至少没有一个拥有其他类型的属性,那么您可以放弃doSomething并使用其他相交方法。这将产生有效的Omit子类型,并且一切都很好... T可以在T & {doSomething(): void}可以使用的任何地方使用。如果T恰好具有T属性,则交集会产生无用的doSomething类型,因此我们应该禁止这样做:

never

我们可以使用它:

// Give up on Omit
class DifferentB<T extends {
    doSomething?: () => void;
    [k: string]: unknown;
}> extends A<T & { doSomething: () => void }> { }

我们不必担心顽皮的不兼容const diffB = new DifferentB<{ x: number }>(); diffB.type.x = 1; // okay diffB.type.doSomething = () => console.log("okay"); // okay 属性:

doSomething

我们可以重新分配构造函数,因为const forbiddenB = new DifferentB<{ x: number, doSomething: number }>(); // error! // number is not ()=>void 确实扩展了typeof DifferentB

typeof A

好的,希望对您有所帮助。祝你好运!

Link to code