【问题标题】:Typescript cannot determine type after 2nd generic indexing第二次通用索引后,打字稿无法确定类型
【发布时间】:2023-01-18 01:19:36
【问题描述】:

我正在尝试编写一个函数,它将接受一个 API 字符串和一个方法,并找出它的请求和响应类型。到目前为止,这是我的尝试。

interface APIDef {
    '/api/api1': { 
        GET: {
            request: {
                api1getparam1: string,
                api1getparam2: number,
            },
            response: {
                api1getres1: string[],
                api1getres2: number[],
            }
        },
        POST: {
            request: {
                api1postparam1: string,
                api1postparam2: number,
            },
            response: {
                api1postres1: string[],
                api1postres2: number[],
            }
        },
    },
    '/api/api2': { 
        GET: {
            request: {
                api2getparam1: boolean,
                api2getparam2: number,
            },
            response: {
                api2getres1: string[],
                api2getres2: number[],
            }
        },
        POST: {
            request: {
                api2postparam1: boolean
            },
            response: {
                api2postres1: 'success',
            }
        },
    },
}

async function callAPI<A extends keyof APIDef, M extends APIDef[A]>(api: A, method: M, request: APIDef[A][M]['request']): Promise<APIDef[A][M]['response']> {
    return await (await fetch(api, {
        method: method,
        body: JSON.stringify(request)
    })).json() as ReturnType<typeof callAPI<A, M>>;
}

但是打字稿似乎无法确定 APIDef[A][M] 的类型,尽管我看不出有任何原因无法确定。这似乎是known issue。作为解决方法,我尝试了以下方法,其中 method 是固定的联合类型:

interface APIDef {
    '/api/api1': { 
        GET: {
            request: {
                api1getparam1: string,
                api1getparam2: number,
            },
            response: {
                api1getres1: string[],
                api1getres2: number[],
            }
        },
        POST: {
            request: {
                api1postparam1: string,
                api1postparam2: number,
            },
            response: {
                api1postres1: string[],
                api1postres2: number[],
            }
        },
        PUT: never,
        PATCH: never,
        DELETE: never,
        HEAD: never,
    },
    '/api/api2': { 
        GET: {
            request: {
                api2getparam1: boolean,
                api2getparam2: number,
            },
            response: {
                api2getres1: string[],
                api2getres2: number[],
            }
        },
        POST: {
            request: {
                api2postparam1: boolean
            },
            response: {
                api2postres1: 'success',
            }
        },
        PUT: never,
        PATCH: never,
        DELETE: never,
        HEAD: never,
    },
}


type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD';

async function callAPI<A extends keyof APIDef, M extends Method>(api: A, method: M, request: APIDef[A][M]['request']): Promise<APIDef[A][M]['response']> {
    return await (await fetch(api, {
        method: method,
        body: JSON.stringify(request)
    })).json() as ReturnType<typeof callAPI<A, M>>;
}

这可行,但要求所有 API 都具有所有方法,即使它们未被使用也是如此。我如何重写通用代码,以便不必在所有 API 中包含所有方法就可以摆脱困境?

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    在这种情况下,我写了这种支票:

    K extends keyof T ? T[K] : never;
    

    由于这是在函数参数位置完成的,因此您可以放心,如果找不到值,用户输入将被拒绝,因为类型将为 never

    您需要做的就是编写一个递归实用程序类型,重复检查每个属性访问。

    type Get<Path extends string[], T> =
        Path extends [] ? T
        : Path[0] extends keyof T
            ? Get<Path extends [string, ...infer R extends string[]] ? R : [], T[Path[0]]>
            : never
    
    async function callAPI<A extends keyof APIDef, M extends string & keyof APIDef[A]>(
      api: A,
      method: M,
      request: Get<[A, M, "request"], APIDef>
    ): Promise<Get<[A, M, "response"], APIDef>> {
      const requestObject = ["GET", "HEAD"].includes(method)
        ? { method: method }
        : {
            method: method,
            body: JSON.stringify(request)
          };
    
      return fetch(api, requestObject)
        .then((response) => response.json())
        .catch(console.error);
    }
    
    const ok = callAPI(
      "/api/api1",
      "GET",
      { api1getparam1: "foo", api1getparam2: 1 }
    );
    

    playground

    我冒昧地更改了您的实现,因为您只是关闭了类型检查,但您确实做到了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-06-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-15
      • 2020-04-09
      • 2023-02-07
      相关资源
      最近更新 更多