打字稿:条件返回类型

时间:2021-07-08 06:52:38

标签: typescript

function customFetch<K extends keyof typeof PATH>(k:K, ...p:Parameters<typeof PATH[K]>):IReturnType[K]{
  const url = "baseurl" + (PATH[k] as any)(...p);
  return fetch(url);
}

function fetch(url:string):any{
  console.log("fetching "+url);
}

const PATH = {
  user: (id:number)=> `/user/${id}`,
  company: ()=> `/company`,
}

type IReturnType={
  user:UserType,
  company:CompanyType,
}

type UserType ={
  name:string,
  age:number
};

type CompanyType ={
  name:string,
  location:string
};

const u = customFetch("user",1);
const c = customFetch("company");

我正在尝试为 customFetch 编写条件返回类型。到目前为止,我能够使它工作并获得所需的函数调用签名。但是对于维护,我有两个无法解决的问题。

  1. 在customFetch 中,没有任何,我得到传播参数必须具有元组类型或传递给其余参数 错误。输入参数类型应该足以保证正确调用路径函数。我该如何解决这个问题?

  2. Path 和 IReturnType 有 1 对 1 的关系。如果我向 Path 添加新条目,则会出现编译错误。但是如果我在 IReturnType 中有额外的条目,则不会检测到它。如何在保持其值的同时强制 IReturnType 具有与 Path 相同的键?

编辑:

感谢您的建议。 似乎没有一种简单的方法来修复第一个。 第二,我使用了一个虚拟变量。

type Bind<A, B> = Omit<A & B, keyof (A | B)>;
const rule2:Bind<typeof PATH, IReturnType> = {};

2 个答案:

答案 0 :(得分:0)

<块引用>

Path 和 IReturnType 有 1 对 1 的关系。

确实如此,但是,TS 无法绑定它们,因为 kp 参数是单独的数据结构。

我的意思是,它们不是一个对象或数组的一部分。

为了帮助 TS bind 他们,你应该创建一个联合类型。

像这样type Union = ["user", number] | ["company", undefined]

const PATH = {
  user: (id: number) => `/user/${id}`,
  company: () => `/company`,
}
type PATH = typeof PATH

type Values<T> = T[keyof T]

type Union = Values<{
  [P in keyof typeof PATH]: [P, Parameters<PATH[P]>[0]]
}>

现在,TS 能够找到它们之间的关系:

const PATH = {
  user: (id: number) => `/user/${id}`,
  company: () => `/company`,
}
type PATH = typeof PATH

type Values<T> = T[keyof T]

type Union = Values<{
  [P in keyof typeof PATH]: [P, Parameters<PATH[P]>[0]]
}>

const BASE = "baseurl"

function customFetch(props: Union) {
  if (props[0] === 'user') {
    return fetch(`${BASE}${PATH[props[0]](props[1])}`)
  } else {
    return fetch(`${BASE}${PATH[props[0]]()}`)
  }
}

但是我们仍然有 ReturnType 的问题。为此,我们可以使用 function-overloads

请记住,函数交叉也会产生c重载:

const PATH = {
  user: <T extends number>(id: T) => `/user/${id}` as const,
  company: () => `/company`,
}
type PATH = typeof PATH


const BASE = "baseurl"

// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I
) => void
  ? I
  : never;

type Values<T> = T[keyof T]

type Union = Values<{
  [P in keyof typeof PATH]: Parameters<PATH[P]>[0] extends undefined ? [P] : [P, Parameters<PATH[P]>[0]]
}>

type Overloading = UnionToIntersection<Values<{
  [P in keyof typeof PATH]: Parameters<PATH[P]>[0] extends undefined ? (key: P) => IReturnType[P] : (key: P, p: Parameters<PATH[P]>[0]) => IReturnType[P]
}>>

const customFetch: Overloading = (...props: Union) => {
  if (props[0] === 'user') {
    return fetch(`${BASE}${PATH[props[0]](props[1])}`)
  } else {
    return fetch(`${BASE}${PATH[props[0]]()}`)
  }
}

function fetch(url: string): any {
  return null as any
}

type IReturnType = {
  user: UserType,
  company: CompanyType,
}

type UserType = {
  name: string,
  age: number
};

type CompanyType = {
  name: string,
  location: string
};

const u = customFetch("user", 1);
const c = customFetch('company');

Playground

答案 1 :(得分:0)

您的第二个问题的解决方案非常简单。你只需要去掉单独的 IReturnType:

function customFetch<
  K extends keyof typeof PATH
>(k: K, ...p: Parameters<typeof PATH[K]>): ReturnType<typeof PATH[K]> {
  const url = "baseurl" + (PATH[k] as any)(...p);
  return fetch(url);
}

playground link

至于第一个问题。我相信如果没有详尽的类型缩小,现在在打字稿中是不可能的。虽然在调用站点打字稿可以获取 k 的具体值并推断其余类型,但在函数体内部没有具体类型 kp 具有类型 Parameters<typeof PATH[K]>取决于未指定泛型类型参数 K。因此,在它知道 K 的确切类型之前(您可以通过类型缩小来实现),它具有无法推理的不透明的非简化类型。