给予
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”方法?
答案 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
好的,希望对您有所帮助。祝你好运!