首页 > 解决方案 > TypeScript 中对象值类型的交集

问题描述

我有一个 TypeScript interface,它定义了我可以在 REST 端点上使用的 HTTP 方法和查询参数类型(请参阅人行横道,了解您为什么要这样做的详细信息):

interface API {
  '/endpoint': {
    get: {a?: string, b?: string};
    post: {b?: string, c?: string};
  } 
}

我想定义一个函数,它采用这种 API 类型、端点 ( '/endpoint') 以及可选的 HTTP 动词 ( 'get','post'等) 和适当的查询参数:

const urlMaker = apiUrlMaker<API>();
urlMaker('/endpoint')({a: 'a', b: 'b'});  // should be an error, we don't know that "a" is OK.
urlMaker('/endpoint')({b: 'b'});  // fine, "b" is always safe. Should return "/endpoint?b=b".
urlMaker('/endpoint', 'get')({a: 'a', b: 'b'});
// fine, we're explicitly using get. Should return "/endpoint?a=a&b=b".

在用户省略 HTTP 动词的情况下,我想接受此方法的查询参数类型的交集(即可以传递给任何动词的查询参数类型)。

我可以通过这种方式获得类型的联合

type ParamUnion = API['/endpoint'][keyof API['/endpoint']];
// type is {a?: string, b?: string} | {b?: string, c?: string}

我知道将 unions 转换为 intersections的技巧,但我想知道在这种情况下是否有一种方法可以不通过联合体。当然,除了getpost( delete, put,patch等) 之外,还可以为端点定义任意数量的 HTTP 动词。

标签: typescript

解决方案


我会使用相同的基本技术,因为据我所知,条件类型推断 withinfer是 TS 中唯一会自动生成任意数量类型的交集的功能(无需求助于递归)。您不必先明确地通过联合,尽管它仍然隐含在其中:

type ParamIntersection = {
  [K in keyof API['/endpoint']]: (x: API['/endpoint'][K]) => void
}[keyof API['/endpoint']] extends
  (x: infer I) => void ? I : never;

/* type ParamIntersection = {
    a?: string | undefined;
    b?: string | undefined;
} & {
    b?: string | undefined;
    c?: string | undefined;
} */

我将每个属性类型转换为函数的参数,然后获取这些函数的并集,并从中推断出单个参数,这通过函数参数逆变的魔力将并集变成了交集。

这种类型有点难看,如果有更多的交集构成会变得更糟,所以你也可以将它们合并成一个对象类型:

type ParamMerged = {
  [K in keyof API['/endpoint']]: (x: API['/endpoint'][K]) => void
}[keyof API['/endpoint']] extends
  (x: infer I) => void ? { [K in keyof I]: I[K] } : never;

/* type ParamIntersection = {
    a?: string | undefined;
    b?: string | undefined;
    c?: string | undefined;
} */

Playground 代码链接


推荐阅读