为什么typescript显示我的更具体类型的错误

为什么typescript显示我的更具体类型的错误,typescript,types,Typescript,Types,我正在为我正在创建/使用的api创建一个通用api处理程序 我已经为端点引用的所有可能方式定义了类型,现在正在为每个端点创建对象 我创建了一个基于代理的通用解决方案,用于实际调用api(下面只显示了其中的一部分) 这只是一个例子,因为它被划分在不同的名称空间和文件中 我遇到的问题是,当我开始为我的ApiService分配可能的路由时,我得到一个类型错误 Type 'IRoute' is not assignable to type 'IApiService'. Property 'TestIte

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

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

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

这只是一个例子,因为它被划分在不同的名称空间和文件中

我遇到的问题是,当我开始为我的
ApiService
分配可能的路由时,我得到一个类型错误

Type 'IRoute' is not assignable to type 'IApiService'.
Property 'TestItem' is missing in type 'IRoute'.
我不明白为什么
TestItem
IRoute
上“丢失”了,因为我有一个通用键,
TestItem
与通用
[string]:IRoute
索引匹配

也许我错过了什么。我可以
返回makeApiProxy(“/api”)作为任何一个函数以清除错误,但我尽量不这样做,我更希望了解我所误解的机制

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));
type with-paramsquerybody=(params:params,query:query,body:body)=>Promise;
输入WithBody=(body:body)=>body;
键入WithParams=(params:params)=>Promise;
使用paramsbody=(params:params,body:body)=>Promise输入;
键入WithParamsQuery=(params:params,query:query)=>Promise;
输入WithQuery=(查询:query)=>Promise;
输入WithNone=()=>Promise;
键入UnknownApiCall=(…参数:any[])=>any;
接口路由{
删除?:WithParams | WithParamsQuery | WithQuery;
获取?:WithParams | WithParamsQuery | WithQuery | WithNone;
补丁?:带主体|带参数主体|带参数主体;
Post?:带主体|带参数主体|带参数主体;
[键:字符串]:IRoute |函数;
}
接口ITestItemRoute扩展IRoute{
Get:WithQuery&WithParams;
职位:WithBody;
分项:{
Get:WithParams;
职位:WithBody;
};
}
接口IApiService{
测试项目:ITestItemRoute;
}
函数ApiService($http:any):IApiService{
函数ApiGet(路径:字符串){
return(…args:any[])=>{
返回$http.get(路径,参数);
};
}
函数ApiPost(路径:字符串){
return(…args:any[])=>{
返回$http.get(路径,参数);
};
}
函数makeApiProxy(路径:字符串):IRoute{
返回新的代理({}{
get:(目标,名称)=>{
交换机(名称){
案例“Get”:
返回ApiGet(路径);
案例“职位”:
返回ApiPost(路径);
违约:
返回makeApiProxy(`${path}/${name}`);
}
},
});
}
/**
*类型“IRoute”不可分配给类型“IApiService”。
*类型“IRoute”中缺少属性“TestItem”。
* 
*但是为什么呢?IRoute上有通用的[key:string]索引!
*/
返回makeApiProxy(“/api”);
}
//我们将传入我们的$http服务
const api=ApiService({}如有);
api.TestItem.Get([12]),然后((TestItem:any)=>console.log(TestItem));

将这一点归结到您所问的具体问题是很有启发性的:

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

注意,我们未能将类型为
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'.
投诉是类型
I
中缺少
“foo”
。索引签名有帮助吗?没有。因此:

const goodIbadO = { baz: 4 };
i = goodIbadO; // okay 
o = goodIbadO; // error
你看,
goodIbadO
是一个完全有效的
I
,因为
“baz”
是一些字符串键,属性是一个
数字
值。但它绝对不是可接受的
O
,因为它缺少必要的
“foo”
“bar
”键

而且,由于并非每个
I
都是
O
,因此通常不能将
I
值分配给
O
变量



出于同样的原因,
IRoute
不可分配给
IApiService
。希望有帮助;祝你好运

这是有道理的。非常感谢。总之,您可以将特定的内容指定给更通用的内容,但不能将通用的内容指定给更具体的内容。
const goodIbadO = { baz: 4 };
i = goodIbadO; // okay 
o = goodIbadO; // error