为什么打字稿会针对我更具体的类型

时间:2017-11-07 15:44:27

标签: typescript types

我正在为我正在创建/使用的API创建通用api处理程序。

我为端点可以引用的所有可能方式定义了类型,现在正在为每个端点创建对象。

我创建了一个基于代理的通用解决方案,用于实际调用api(其中只有一部分如下所示)。

这只是例如,因为它分为不同的命名空间和文件。

我遇到的问题是当我开始将可能的路线分配给ApiService时,我收到类型错误。

Type 'IRoute' is not assignable to type 'IApiService'.
Property 'TestItem' is missing in type 'IRoute'.

我不明白为什么TestItem是"缺少"与IRoute上一样,我有一个通用密钥,TestItem与通用[string]: IRoute索引相匹配。

也许我错过了什么。我可以return return makeApiProxy("/api") as any;清除错误,但我尽量不这样做,我宁愿理解我误解的机制。

type WithParamsQueryBody<Params extends any[], Query, Body, Response> = (params: Params, query: Query, body: Body) => Promise<Response>;
type WithBody<Body, Response> = (body: Body) => Body;
type WithParams<Params extends any[], Response> = (params: Params) => Promise<Response>;
type WithParamsBody<Params extends any[], Body, Response> = (params: Params, body: Body) => Promise<Response>;
type WithParamsQuery<Params extends any[], Query, Response> = (params: Params, query: Query) => Promise<Response>;
type WithQuery<Query, Response> = (query: Query) => Promise<Response>;
type WithNone<Response> = () => Promise<Response>;
type UnknownApiCall = (...params: any[]) => any;

interface IRoute {
    Delete?: WithParams<any[], any> | WithParamsQuery<any[], any, any> | WithQuery<any, any>;
    Get?: WithParams<any[], any> | WithParamsQuery<any[], any, any> | WithQuery<any, any> | WithNone<any>;
    Patch?: WithBody<any, any> | WithParamsBody<any[], any, any> | WithParamsQueryBody<any[], any, any, any>;
    Post?: WithBody<any, any> | WithParamsBody<any[], any, any> | WithParamsQueryBody<any[], any, any, any>;

    [key: string]: IRoute | Function;
}

interface ITestItemRoute extends IRoute {
    Get: WithQuery<any, any[]> & WithParams<[number], any>;
    Post: WithBody<any, any>;
    SubItem: {
        Get: WithParams<[number], any[]>;
        Post: WithBody<any, any>;
    };
}

interface IApiService {
    TestItem: ITestItemRoute;
}

function ApiService($http: any): IApiService {
    function ApiGet(path: string) {
        return (...args: any[]) => {
            return $http.get(path, args);
        };
    }

    function ApiPost(path: string) {
        return (...args: any[]) => {
            return $http.get(path, args);
        };
    }

    function makeApiProxy(path: string): IRoute {
        return new Proxy({}, {
            get: (target, name) => {
                switch (name) {
                    case "Get":
                        return ApiGet(path);
                    case "Post":
                        return ApiPost(path);
                    default:
                        return makeApiProxy(`${path}/${name}`);
                }

            },
        });
    }

    /**
     * Type 'IRoute' is not assignable to type 'IApiService'.
     * Property 'TestItem' is missing in type 'IRoute'.
     * 
     * But why? IRoute has the generic [key: string] index on it!
     */
    return makeApiProxy("/api");
}


// We would pass in our $http service
const api = ApiService({} as any);

api.TestItem.Get([12]).then((testItem: any) => console.log(testItem));

1 个答案:

答案 0 :(得分:1)

将此问题解决为您所询问的具体问题是有益的:

让我们看看两个接口:I具有number属性的索引签名,O是一个更“普通”的对象,具有两个number属性,其中的键名为"foo""bar"

interface I {
  [key: string]: number;
}
interface O {
  foo: number;
  bar: number;
}

注意我们未能将I类型的值分配给O类型的变量:

declare let i: I;
let o: O = i; // error: 
// Type 'I' is not assignable to type 'O'.  
// Property 'foo' is missing in type 'I'.

投诉是"foo"类型中缺少I。索引签名不帮助吗?不。因此:

const goodIbadO = { baz: 4 };
i = goodIbadO; // okay 
o = goodIbadO; // error

您看,goodIbadO是完全有效的I,因为"baz"是一些字符串键,属性是number值。但它绝对是可接受的O,因为它缺少必要的"foo""bar“`键。

由于并非每个I都是O,因此您无法将I值分配给O变量。

并且,出于同样的原因,IRoute无法分配给IApiService。希望有所帮助;祝你好运!