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 编写条件返回类型。到目前为止,我能够使它工作并获得所需的函数调用签名。但是对于维护,我有两个无法解决的问题。
在customFetch 中,没有任何,我得到传播参数必须具有元组类型或传递给其余参数 错误。输入参数类型应该足以保证正确调用路径函数。我该如何解决这个问题?
Path 和 IReturnType 有 1 对 1 的关系。如果我向 Path 添加新条目,则会出现编译错误。但是如果我在 IReturnType 中有额外的条目,则不会检测到它。如何在保持其值的同时强制 IReturnType 具有与 Path 相同的键?
编辑:
感谢您的建议。 似乎没有一种简单的方法来修复第一个。 第二,我使用了一个虚拟变量。
type Bind<A, B> = Omit<A & B, keyof (A | B)>;
const rule2:Bind<typeof PATH, IReturnType> = {};
答案 0 :(得分:0)
Path 和 IReturnType 有 1 对 1 的关系。
确实如此,但是,TS 无法绑定它们,因为 k
和 p
参数是单独的数据结构。
我的意思是,它们不是一个对象或数组的一部分。
为了帮助 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');
答案 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);
}
至于第一个问题。我相信如果没有详尽的类型缩小,现在在打字稿中是不可能的。虽然在调用站点打字稿可以获取 k
的具体值并推断其余类型,但在函数体内部没有具体类型 k
而 p
具有类型 Parameters<typeof PATH[K]>
取决于未指定泛型类型参数 K
。因此,在它知道 K
的确切类型之前(您可以通过类型缩小来实现),它具有无法推理的不透明的非简化类型。