我有一个这样的模式,我在代码中使用A LOT,我在几个月(甚至几年)之前使用旧版本的typescript编译器编写,可能是1.8 - 2.2:
interface IBase { };
interface IExtends extends IBase {
key2: string;
};
abstract class MyAbstractClass {
protected abstract myFunc<T extends IBase>(arg: string): T;
};
class MyClass extends MyAbstractClass {
protected myFunc(arg: string): IExtends {
return { key2: arg };
}
};
当我写这段代码时,Typescript根本就没有抱怨,并按照你的预期处理了这个问题。
但是现在,typescript(版本2.8.1)抱怨:
src/errorInvestigation.ts(12,15): error TS2416: Property 'myFunc' in type 'MyClass' is not assignable to the same property in base type 'MyAbstractClass'.
Type '(arg: string) => IExtends' is not assignable to type '<T extends IBase>(arg: string) => T'.
Type 'IExtends' is not assignable to type 'T'.
如果打字稿在将此标记为错误时是正确的,那么完成相同操作的正确方法是什么?
非常感谢。
答案 0 :(得分:2)
我认为你可能并不认为myFunc
是通用的。以下签名
abstract class MyAbstractClass {
protected abstract myFunc<T extends IBase>(arg: string): T;
};
表示MyAbstractClass
有myFunc()
方法,该方法接受string
输入并返回调用者指定的IBase
的任何子类型。但是您希望它返回实现者指定的IBase
子类型。如果myFunc
不是protected
,您可以快速看到问题
declare const myAbstractInstance: MyAbstractClass;
let iBase = myAbstractInstance.myFunc<IBase>('hey'); // IBase
let iExtends = myAbstractInstance.myFunc<IExtends>('hey'); // IExtends?!
let iOther = myAbstractInstance.myFunc<IBase & {foo: string}>('hey'); // What?!
iOther = iExtends; // error, not assignable
使用相同的参数调用相同的函数三次,并且在运行时它们将具有相同的输出,但显然TypeScript认为输出具有不同的类型。这是一种奇怪的模式,而不是你打算传达的东西。
而且我不确定它何时被强制执行,但你不应该被允许用非泛型函数覆盖泛型函数,除非非泛型函数确实是泛型函数的子类型一。但由于T
可以是调用者指定的IBase
的任何子类型,因此唯一可分配给每个可能的T
的非泛型类型将是{ {1}}和never
。我想你并不想归还那些。
听起来你只是希望any
返回子类实现指定的myFunc
或某个子类型。如果是这样,那么你可以完全没有泛型:
IBase
这对你有用。子类
abstract class MyAbstractClass {
protected abstract myFunc(arg: string): IBase;
};
与class MyClass extends MyAbstractClass {
protected myFunc(arg: string): IExtends {
return { key2: arg };
}
};
兼容,因为方法返回类型为covariant,这意味着如果MyAbstractClass
是Y
的子类型,那么X
的方法是允许返回Y
的类似方法的子类型。
如果您需要方法参数在子类中更窄,这将是不同的,因为方法参数是逆变的。如果X
是T
的类型而不是返回类型,则上述不应该工作......但确实正好工作,因为方法参数仍被视为bivariant。但我认为这不是你的用例。 (更新:我看到是你的用例。显然,方法参数因为这个原因是双变量的,所以试试吧。我个人不喜欢双重性,而且还有更多&#34;纠正&#34;解决方法,但它们可能比它们的价值更麻烦。)
希望有所帮助;祝你好运。
答案 1 :(得分:1)
非常感谢jcalz让我开始了研究之路,导致一个令人遗憾但可行的答案
我仍然感到困惑的是为什么打字稿会做出这种改变,因为我认为描述这种改变的原始方式没有问题。
答案是将泛型提升到类级别,而不是将它们留在方法上。这有利有弊,在我看来比专业人士更有利。优点是抽象类的主体可能更清晰,更容易阅读。缺点是如果你有许多不同类型需要在同一个类中处理,那么这个类本身需要在类声明中声明所有这些类型,并且每个派生类也需要这样做,这意味着这更难阅读我认为。但它会完成这项工作。
这是我看到的完整答案,我希望任何人(包括jcalz!)能够进一步思考/改进:
interface IArgBase {
baseArgKey: string;
};
interface IReturnBase {
baseReturnKey: string;
};
interface IPromiseBase {
basePromiseKey: string;
};
interface IArgExtends extends IArgBase {
extendedArgKey: string;
};
interface IReturnExtends extends IReturnBase {
extendedReturnKey: string;
};
interface IPromiseExtends extends IPromiseBase {
extendedPromiseKey: string;
};
abstract class MyAbstractClass<TArg extends IArgBase, TReturn extends IReturnBase, TPromise extends IPromiseBase>{
protected abstract myFunc(arg: TArg): TReturn;
protected abstract myAsyncFunc(arg: TArg): Promise<TPromise>;
};
class MyClass extends MyAbstractClass<IArgExtends, IReturnExtends, IPromiseExtends> {
protected myFunc(arg: IArgExtends): IReturnExtends {
return { baseReturnKey: arg.baseArgKey, extendedReturnKey: arg.extendedArgKey };
};
protected myAsyncFunc(arg: IArgExtends): Promise<IPromiseExtends> {
return Promise.resolve({ basePromiseKey: arg.baseArgKey, extendedPromiseKey: arg.extendedArgKey });
};
};