请参阅代码here(也在本文末尾列出)
我不确定为什么get
函数没有强类型。
定义为get:(id:string) => IPromise<Test>;
但是,我将其指定为
constructor(srv:Store<TestData>){
this.get = (id) => this.getImp(srv, id);
}
private getImp(srv:Store<TestData>, d:string) {
return srv.get("/test")
.then( (d) => { //d == IPromise<getImp>
var rep = d.reply; //rep == TestData
//var rep:string = "" gives error
return rep;
});
}
getImp
返回带有类型参数TestData
的prmise(get结果)。但这不应与Test
兼容,因此与get
兼容。为什么没有错误?
如果我将getImp
的签名更改为
private getImp(srv:Store<TestData>, d:string):IPromise<TestData> {
如何将getImp(srv:Store<TestData>, d:string):IPromise<TestData>
分配给get:(id:string) => IPromise<Test>
IPromise的定义有什么问题吗?
感谢。
export interface IGetResult<T> {
reply: T
}
export class Store<T>{
get:(r:any) => IPromise<IGetResult<T>>;
}
export interface TestData {
Id: string
Name : string
}
export interface ITest extends TestData{
test():void
}
export class Test implements ITest {
Id:string;
Name:string;
test(){}
}
export class service{
get:(id:string) => IPromise<Test>;
constructor(srv:Store<TestData>){
this.get = (id) => this.getImp(srv, id);
}
private getImp(srv:Store<TestData>, d:string):IPromise<TestData> {
return srv.get("/test")
.then( (d) => { //d == IPromise<TestData>
var rep = d.reply; //rep == TestData
//var rep:string = "" gives error
return rep;
});
}
}
//========== angular.d.ts ===========
// from https://github.com/borisyankov/DefinitelyTyped/blob/master/angularjs/angular.d.ts
export interface IRequestConfig {
method: string;
url: string;
params?: any;
// XXX it has it's own structure... perhaps we should define it in the future
headers?: any;
cache?: any;
withCredentials?: boolean;
// These accept multiple types, so let's define them as any
data?: any;
transformRequest?: any;
transformResponse?: any;
timeout?: any; // number | promise
}
export interface IHttpPromiseCallback<T> {
(data: T, status: number, headers: (headerName: string) => string, config: IRequestConfig): void;
}
export interface IHttpPromiseCallbackArg<T> {
data?: T;
status?: number;
headers?: (headerName: string) => string;
config?: IRequestConfig;
}
export interface IHttpPromise<T> extends IPromise<T> {
success(callback: IHttpPromiseCallback<T>): IHttpPromise<T>;
error(callback: IHttpPromiseCallback<T>): IHttpPromise<T>;
then<TResult>(successCallback: (response: IHttpPromiseCallbackArg<T>) => TResult, errorCallback?: (response: IHttpPromiseCallbackArg<T>) => any): IPromise<TResult>;
then<TResult>(successCallback: (response: IHttpPromiseCallbackArg<T>) => IPromise<TResult>, errorCallback?: (response: IHttpPromiseCallbackArg<T>) => any): IPromise<TResult>;
}
export interface IPromise<T> {
then<TResult>(successCallback: (promiseValue: T) => IHttpPromise<TResult>, errorCallback?: (reason: any) => any, notifyCallback?: (state: any) => any): IPromise<TResult>;
then<TResult>(successCallback: (promiseValue: T) => IPromise<TResult>, errorCallback?: (reason: any) => any, notifyCallback?: (state: any) => any): IPromise<TResult>;
then<TResult>(successCallback: (promiseValue: T) => TResult, errorCallback?: (reason: any) => TResult, notifyCallback?: (state: any) => any): IPromise<TResult>;
catch<TResult>(onRejected: (reason: any) => IHttpPromise<TResult>): IPromise<TResult>;
catch<TResult>(onRejected: (reason: any) => IPromise<TResult>): IPromise<TResult>;
catch<TResult>(onRejected: (reason: any) => TResult): IPromise<TResult>;
finally<TResult>(finallyCallback: ()=>any):IPromise<TResult>;
}
答案 0 :(得分:4)
TypeScript中的泛型具有不同的设计。在某些情况下,他们是双变量WRT他们的类型参数,并且你期望他们在这种特殊情况下是协变的。承诺是不变的,所以它们确实应该与他们持有的类型一致。
要看到它,改变
export interface ITest extends TestData
到
export interface ITest
然后从Name:string;
中移除属性class Test
。如果你这样做,那么打字稿最终会抱怨它“无法将IPromise<TestData>
转换为IPromise<Test>
”,因为Test
和[{1}}都不能导出TestData
来自TestData
。
更直接地说明了这个错误:
Test
只要Test继承自TestData 或 TestData继承自Test,这仍然有效。
术语协变和双变量的解释
泛型类型是协变WRT的类型参数,如果它的层次结构与类型参数的层次结构一起变化。例如:
如果
var x: IPromise<Test> = <IPromise<TestData>><any>{}
协变 WRT类型Array<T>
和T
是ExtendedItem
然后
Item
被接受为Array<ExtendedItem>
。如果子类型是双向的,那么泛型类型是双变量WRT的类型参数:
如果
Array<Item>
bivariant WRT类型Array<T>
和T
是ExtendedItem
然后
Item
被接受为Array<ExtendedItem>
和Array<Item>
被接受为Array<Item>
和最后注意事项:为了保证类型安全,可变数组实际上应该不变 WRT到参数。许多语言(如C#或Java)使其协变 - 但在这种情况下,它可以Array<ExtendedItem>
成为push(item:OtherExtendedItem);
数组而不会在编译时被捕获为错误。
编辑:“有时候是双变的”是什么意思呢?这意味着typescript实际上没有任何硬编码的方差规则。当遇到泛型类型的特化时,它只应用该类型参数。执行此操作后,它会对结果类型执行结构检查 - 如果匹配,则一切正常。请考虑以下示例:
ExtendedItem
编译器认为此代码有效。该类型是否专门用于T = AB或T = ABC不会改变结果类型的结构,这只是{x:number}
如果我们在Box中有一个返回Box的方法会怎样?让我们添加该方法,并参见:
class Box<T>{
public x: number;
constructor(t:T) {
this.x = 1;
}
}
interface AB { a: number; b: number; }
interface ABC { a: number; b: number; c: number; }
var ab:Box<AB> = new Box({a: 1, b: 2});
var y:Box<ABC> = ab;
同样,如果我们应用专门化,我们会得到一个无限的递归结构:
getThis():Box<T> { return this; }
无论专业化程度如何,最终都是相同的。
但是,如果我们添加另一种方法,那就是返回类型为T的值:
{x: number, getThis: () => {x : number, getThis: () => ... }}
然后,如果我们应用专门化T = AB,则结果如下:
getValue():T { return <T>{}; }
与专业化T = ABC
不兼容{x: number; y: () => { a: number; b number; } }
并且编译器最终会抱怨。
此时,您可能会注意到{x: number; y: () => { a: number; b: number; c: number; } }
与IPromise<T>
不太相似。实际上,它没有返回类型T值的方法,但它有一些方法(如Box<T>
和then
),它们采用类型为T的值的回调。让我们添加一个类似的方法到盒子
catch
现在让我们应用类型专精化then<U>(f: (val: T) => Box<U>): Box<U>;
T = AB
和{x: number; then: (val: {a: number; b: number;}) => Box<{x: number; then: ...}}
T = ABC
这些不应该兼容!
这导致我们在TypeScript中遇到真正的问题:函数类型(“callbacks”)的参数是双变量WRT它们的输入参数,它们应该是逆变的。
这是一个演示问题的简单示例:
{x: number; then: (val: {a: number; b: number; c: number;}) => Box<{x: number; then: ...}}
在此示例中,采用类型IA的参数的回调无法安全地替换为采用IB类型参数的回调(当IB扩展IA时)。与示例中一样,采用IB类型参数的回调可能会尝试调用方法foo(),这意味着将类型IA的参数传递给该回调将导致类型错误
对您的问题最快的解决方法是找到一个返回类型为T的值的IPromise成员:
interface IA { bar(): void }
interface IB { bar(): void; foo(): void; }
function fn(passedFn: (a: IA) => void) {
var a:IA = {bar: function(){}};
passedFn(a);
}
// compiles but throws TypeError at runtime!
fn((b: IB) => b.foo());
当然这会使接口与其他promise类型(或库)不兼容,所以这里真正的解决方法是让TypeScript纠正回调的行为。