从函数参数推断泛型类型之一 [英] Infer one of generic types from function argument

查看:29
本文介绍了从函数参数推断泛型类型之一的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下示例.fetchItems 函数根据传递的 onlyBody 参数返回响应或响应正文,该参数默认为 true.

接口HttpResponse{身体:T}function fetchItems(url: string, onlyBody: B = true as B) {回报承诺.resolve({body: 'some data'} as any).then<B 扩展为真?T : HttpResponse>(res => onlyBody ? res.body : res);}

如果传递了两种泛型,则函数按预期工作

const a = fetchItems('url', false)//Promise>const b = fetchItems('url', true)//Promiseconst c = fetchItems('url')//Promise

我想放弃传递 B 类型的要求,因为它相对于 onlyBody 参数是多余的.但是当 B 类型没有显式传递时,ts 编译器会抱怨它(预期 2 个类型参数,但得到 1 个).

const e = fetchItems('url', false);//想要 Promise>const f = fetchItems('url', true)//想要 Promiseconst g = fetchItems('url')//想要 Promise

我尝试将功能签名更改为:

function fetchItems(url: string, onlyBody: B = true as B) {

但是在 e 示例中有一个错误:'false' 类型的参数不能分配给 'true' 类型的参数 |未定义'

有什么方法可以改变函数签名,使 e, f, g 示例与 a, b, c 一样工作?演示:https://stackblitz.com/edit/typescript-ydkmzk

解决方案

您面临的主要问题是 TypeScript 不支持 部分类型参数推断.要么您必须手动指定所有类型参数(具有默认值的类型参数除外),要么让编译器推断所有类型参数,但您不能指定一些并让编译器推断其余部分.

使用重载而不是泛​​型类型参数,如@Nenad 的答案所示,是解决boolean 具有少量可能的值.注释中提到的带有 boolean 参数(而不是 truefalse 参数)的问题可以通过添加另一个重载来解决,就像这样:

function fetchItems(网址:字符串,onlyBody: 假): Promise;function fetchItems(url: string, onlyBody?: true): Promise;//添加这个重载函数 fetchItems(网址:字符串,onlyBody:布尔值): 承诺;function fetchItems(url: string, onlyBody: boolean = true) {return Promise.resolve({ body: "some data" } as any).then(资源=>(onlyBody ? res.body : res));}const a = fetchItems("url", false);//Promise>const b = fetchItems("url", true);//承诺<字符串>const c = fetchItems("url");//承诺<字符串>const d = fetchItems("url", Math.random() <0.5);//承诺>

我知道另外两种解决方法,我一直称之为 Currying 和 Dummying:

<小时>

柯里化"解决方法将您的两个类型参数的单一泛型函数拆分为两个柯里化函数 每个类型参数.一个你指定,另一个你推断.像这样:

const fetchItems = () =><B extends boolean = true>(网址:字符串,onlyBody: B = true as B) =>{return Promise.resolve({ body: "some data" } as any).then<B 扩展为真?T : HttpResponse>(res => (onlyBody ? res.body : res));};

你这样称呼它:

const a = fetchItems()("url", false);//Promise>const b = fetchItems()("url", true);//承诺<字符串>const c = fetchItems()("url");//承诺<字符串>const d = fetchItems()("url", Math.random() <0.5);//承诺>

或者,由于所有这些都使用 fetchItems(),您可以将其保存到自己的函数中并使用它,以减少冗余:

const fetchItemsString = fetchItems();const e = fetchItemsString("url", false);//Promise>const f = fetchItemsString("url", true);//承诺<字符串>const g = fetchItemsString("url");//承诺<字符串>const h = fetchItemsString("url", Math.random() <0.5);//承诺>

<小时>

Dummying"变通方法让编译器推断所有参数类型,甚至是您想要手动指定的参数类型.它通过让函数采用您通常手动指定的类型的虚拟参数来实现这一点;该函数忽略虚拟参数:

function fetchItems(虚拟T:T,网址:字符串,onlyBody: B = true as B){return Promise.resolve({ body: "some data" } as any).then<B 扩展为真?T : HttpResponse>(res => (onlyBody ? res.body : res));}const a = fetchItems("dummy", "url", false);//Promise>const b = fetchItems("dummy", "url", true);//承诺<字符串>const c = fetchItems("dummy", "url");//承诺<字符串>const d = fetchItems("dummy", "url", Math.random() <0.5);//承诺>

由于虚拟值只是为了编译器的利益,在运行时没有使用,你也可以使用 type assertion 假装你有一个类型的实例,而不是通过任何麻烦来创建一个:

const dummy = null!作为字符串;//运行时为空,编译时为字符串const e = fetchItems(dummy, "url", false);//Promise>const f = fetchItems(dummy, "url", true);//承诺<字符串>const g = fetchItems(dummy, "url");//承诺<字符串>const h = fetchItems(dummy, "url", Math.random() <0.5);//承诺>

当然,获取string 值很容易,所以使用null 没有多大意义!as string 而不是 "randomString",但对于更复杂的类型,使用类型断言会变得更方便,而不是尝试创建一个你会扔掉的真实实例.

<小时>

无论如何,希望其中之一对您有用.祝你好运!

链接到代码

Consider the following example. fetchItems function returns response or response body depending on passed onlyBody argument which defaults to true.

interface HttpResponse<T> {
  body: T
}

function fetchItems<T, B extends boolean>(url: string, onlyBody: B = true as B) {
  return Promise
    .resolve({body: 'some data'} as any)
    .then<B extends true ? T : HttpResponse<T>>(res => onlyBody ? res.body : res);
}

If both generic types are passed the function works as expected

const a = fetchItems<string, false>('url', false) // Promise<HttpResponse<string>>
const b = fetchItems<string, true>('url', true)   // Promise<string>
const c = fetchItems<string, true>('url')         // Promise<string>

I'd like to drop requirement of passing B type as it is redundant in relation to onlyBody parameter. But when B type is not explictly passed, ts compiler complains about it (Expected 2 type arguments but got 1).

const e = fetchItems<string>('url', false);        // would want Promise<HttpResponse<string>>
const f = fetchItems<string>('url', true)          // would want Promise<string>
const g = fetchItems<string>('url')                // would want Promise<string>

I tried to change fucntion signature into:

function fetchItems<T, B extends boolean = true>(url: string, onlyBody: B = true as B) {

but then there is an error in e example: Argument of type 'false' is not assignable to parameter of type 'true | undefined'

Is there any way to alter function signature so that e, f, g examples will work the same as a, b, c ? demo: https://stackblitz.com/edit/typescript-ydkmzk

解决方案

The main problem you're facing is that TypeScript does not support partial type parameter inference. Either you must manually specify all type parameters (except for ones with defaults), or you let the compiler infer all type parameters, but you cannot specify some and let the compiler infer the rest.

Using overloads instead of generic type parameters, as shown in @Nenad's answer is one way around this for types like boolean with a small number of possible values. The issue mentioned in the comments with a boolean parameter (instead of a true or false one) can be solved by adding another overload, like this:

function fetchItems<T>(
  url: string,
  onlyBody: false
): Promise<HttpResponse<T>>;
function fetchItems<T>(url: string, onlyBody?: true): Promise<T>;

// add this overload
function fetchItems<T>(
  url: string,
  onlyBody: boolean
): Promise<T | HttpResponse<T>>;

function fetchItems<T>(url: string, onlyBody: boolean = true) {
  return Promise.resolve({ body: "some data" } as any).then(
    res => (onlyBody ? res.body : res)
  );
}

const a = fetchItems<string>("url", false); // Promise<HttpResponse<string>>
const b = fetchItems<string>("url", true); // Promise<string>
const c = fetchItems<string>("url"); // Promise<string>
const d = fetchItems<string>("url", Math.random() < 0.5); 
// Promise<string|HttpResponse<string>>

I know of two other workarounds, which I've been calling Currying and Dummying:


The "Currying" workaround splits your single generic function of two type parameters into two curried functions of one type parameter each. One you specify, the other you infer. Like this:

const fetchItems = <T>() => <B extends boolean = true>(
  url: string,
  onlyBody: B = true as B
) => {
  return Promise.resolve({ body: "some data" } as any).then<
    B extends true ? T : HttpResponse<T>
  >(res => (onlyBody ? res.body : res));
};

And you call it like this:

const a = fetchItems<string>()("url", false); // Promise<HttpResponse<string>>
const b = fetchItems<string>()("url", true); // Promise<string>
const c = fetchItems<string>()("url"); // Promise<string>
const d = fetchItems<string>()("url", Math.random() < 0.5); 
// Promise<string|HttpResponse<string>>

Or, since all of those use fetchItems<string>(), you can save that to its own function and use it, for a bit less redundancy:

const fetchItemsString = fetchItems<string>();
const e = fetchItemsString("url", false); // Promise<HttpResponse<string>>
const f = fetchItemsString("url", true); // Promise<string>
const g = fetchItemsString("url"); // Promise<string>
const h = fetchItemsString("url", Math.random() < 0.5); 
// Promise<string|HttpResponse<string>>


The "Dummying" workaround lets the compiler infer all the parameter types, even the ones you want to specify manually. It does this by having the function take dummy parameters of the types you would normally specify manually; the function ignores the dummy parameters:

function fetchItems<T, B extends boolean = true>(
  dummyT: T,
  url: string,
  onlyBody: B = true as B
) {
  return Promise.resolve({ body: "some data" } as any).then<
    B extends true ? T : HttpResponse<T>
  >(res => (onlyBody ? res.body : res));
}

const a = fetchItems("dummy", "url", false); // Promise<HttpResponse<string>>
const b = fetchItems("dummy", "url", true); // Promise<string>
const c = fetchItems("dummy", "url"); // Promise<string>
const d = fetchItems("dummy", "url", Math.random() < 0.5); 
// Promise<string|HttpResponse<string>>

Since the dummy value is only for the benefit of the compiler and is unused at runtime, you can also use a type assertion to pretend you have an instance of the type instead of going through any trouble to create one:

const dummy = null! as string; // null at runtime, string at compile time

const e = fetchItems(dummy, "url", false); // Promise<HttpResponse<string>>
const f = fetchItems(dummy, "url", true); // Promise<string>
const g = fetchItems(dummy, "url"); // Promise<string>
const h = fetchItems(dummy, "url", Math.random() < 0.5); 
// Promise<string|HttpResponse<string>>

Of course it's pretty easy to get a string value, so there's not much point using null! as string instead of "randomString", but for more complicated types it becomes more convenient to use the type assertion instead of trying to create a real instance that you'll just be throwing away.


Anyway, hope one of those works for you. Good luck!

Link to code

这篇关于从函数参数推断泛型类型之一的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆