从函数参数

时间:2019-08-21 10:00:40

标签: typescript

请考虑以下示例。 fetchItems函数根据传递的onlyBody参数(默认为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);
}

如果两个通用类型都被传递,则该功能将按预期工作

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>

我想放弃传递B类型的要求,因为它相对于onlyBody参数是多余的。但是,如果未明确传递B类型,则ts编译器会抱怨它(期望2个类型参数,但得到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>

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

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

但是在e示例中有一个错误:Argument of type 'false' is not assignable to parameter of type 'true | undefined'

是否有任何方法可以更改函数签名,以使e,f,g示例与a,b,c相同? 演示:https://stackblitz.com/edit/typescript-ydkmzk

2 个答案:

答案 0 :(得分:2)

函数重载可以满足您的需求:

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

Playground

由于here中所述的TypeScript“设计限制”,

具有条件类型的解决方案不起作用。

答案 1 :(得分:1)

您面临的主要问题是TypeScript不支持partial type parameter inference。您必须手动指定所有类型参数(具有默认值的参数除外),或者让编译器推断所有类型参数,但是您不能指定某些参数而让编译器推断其余参数。

对于诸如boolean之类的类型且具有少量可能的值,如@Nenad's answer所示,使用重载代替通用类型参数是一种解决方法。注释中提到的带有boolean参数(而不是truefalse的问题)可以通过添加另一个重载来解决,例如:

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>>

我知道另外两个变通方法,我一直称其为Currying和Dummying:


“ Currying”解决方法将两个类型参数的单个泛型函数拆分为两个curried functions,每个类型参数一个。您指定一个,然后另一个推断。像这样:

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));
};

您这样称呼它:

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>>

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

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>>

通过“虚拟”解决方法,编译器可以推断所有参数类型,甚至包括您要手动指定的参数类型。它通过使函数采用您通常会手动指定的类型的伪参数来实现此目的;该函数将忽略虚拟参数:

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>>

由于伪值仅是为了编译器的利益,并且在运行时未使用,因此您也可以使用type assertion来假装您拥有该类型的实例,而无需费心创建一个实例:

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>>

当然,获取string值很容易,因此使用null! as string代替"randomString"并没有多大意义,但是对于更复杂的类型,使用该类型变得更加方便断言而不是尝试创建将被扔掉的真实实例。


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

Link to code