TS从函数参数对象中推导返回值类型

我们在做网络请求的时候经常会用到拦截器,在返回值拦截器中,我们可能会对返回值进行处理,并返回一个新的类型。因此我们会遇上这种需求:

interface InterceptorOption<T> {
// ignore other props
responseTransformer?: (res: T) => any;
}
function interceptor<T>(instance: (...args: any[]) => Promise<T>, options?: InterceptorOption<T>) {}
const api = () => Promise.resolve({ name: "bobo" });
const res1 = interceptor(api); // typeof res1 === { name: string }
const res2 = interceptor(api, {}); // typeof res2 === { name: string }
const res3 = interceptor(api, { // typeof res3 === { value: string }
responseTransformer(res) {
return {
value: "bobo"
};
}
});

我们希望以上三种函数调用方式都能够推导并返回正确的类型。这里我想讲一讲通过类型体操而不是函数签名重载的方式,来得到我们想要的类型。我们来添加一种InferResponse类型:

type InferResponse<T> = // ...
function interceptor<T>(instance: (...args: any[]) => Promise<T>, options?: InterceptorOption<T>): InferResponse<T> {
// ...
}

下来开始完善InferResponse


首先我们可以发现,interceptor函数的返回值是与options的传参相关的,options传或不传、options中有没有responseTransformer会影响我们的返回值类型。所以options应该作为类型参数传入,也就是说放到泛型中。

interface InterceptorOption<T> {
responseTransformer?: (response: T) => any;
}
function interceptor<T, O extends InterceptorOption<T>>(instance: (...args: any[]) => Promise<T>, options?: O) {}

相同的,InferResponse的泛型中也应该传入一项OInferResponse应该根据O的不同返回不同的类型:

type InferResponse<T, O> = // ...

现在我们要根据O类型的不同做一些判断,并且我们希望从O中取到responseTransformer的返回值类型:

type InferResponse<T, O> = O extends { responseTransformer: (res: any) => any } ? ReturnType<O["responseTransformer"]> : T;

如果O中有responseTransformer并且responseTransformer为函数时,我们取它的返回值ReturnType<O["responseTransformer"]>;否则返回原本的T类型。 完整代码如下:

interface InterceptorOption<T> {
responseTransformer?: (response: T) => any;
}
type InferResponse<T, O> = O extends { responseTransformer: (res: any) => any } ? ReturnType<O["responseTransformer"]> : T;
function interceptor<T, O extends InterceptorOption<T>>(instance: (...args: any[]) => Promise<T>, options?: O) {
return {} as InferResponse<T, O>;
}

此时我们的interceptor便能根据options返回正确的类型。


其实一开始我要写的不是这个,一开始想做的是这种类型体操:

interface InterceptorOption<T> {
// ignore other props
responseTransformer?: (res: T) => any;
}
type InferResponse<T, O> = O extends { responseTransformer: (res: any) => any } ? ReturnType<O["responseTransformer"]> : T;
function interceptor<T, O extends InterceptorOption<T> = InterceptorOption<T>>(options?: O) {
return {} as InferResponse<T, O>;
}
const res1 = interceptor<string>(); // typeof res1 === string
const res2 = interceptor<number>({}); // typeof res2 === number
const res3 = interceptor({ // typeof res3 === { value: string }
responseTransformer(res) {
return {
value: "bobo",
};
},
});
// The problem is here
// Expected res4 is `{ value: string }`, but presenting `boolean`
const res4 = interceptor<boolean>({ // typeof res4 === boolean
responseTransformer(res) {
return {
value: "bobo",
};
},
});

然后翻车了,没跳出来,想了两天也没想出怎么解决。这里有一个TS Playground,感兴趣的可以试一试。如果有人做出来了也欢迎评论!

评论加载中 (ง •̀ω•́)ง