typescript - TypeScript:返回与参数相同类型的泛型函数

考虑一下(不编译):

function roundTo<T = number | null | undefined>(
  num: T,
  decimals: number,
): T {
  if (num === null || num === undefined) return num;

  const factor = Math.pow(10, decimals);
  return (Math.round(num * factor) / factor);
}

我想返回传递给 roundTo() 函数的相同类型。

例如:

const num1: number | null = 1.123456;
roundTo(num1, 1) // return type number | null

const num2: number = 1.123456;
roundTo(num2, 1) // return type number

const num3 = null;
roundTo(num3, 1) // return type null

roundTo 的返回类型在编译时是已知的,因此希望能够根据第一个参数中传递的类型从那里携带类型。

我可以通过 casting 返回类型 as any 进行编译,但这会破坏类型安全。我也可以通过使用 extends 而不是 = 并封装返回类型 as T 来进行编译,但是当传入 nullundefined 时,它具有返回 any 的不良行为。

如何让 TypeScript 表现出所需的行为?

相关:https://stackoverflow.com/a/51195834/188740https://stackoverflow.com/a/57529925/188740

回答1

它应该是 T extends ...,而不是 T = ...。后一种形式是“T 的默认 value”,它不是推断出来的,而是总是被当作声明的,基本上扼杀了整个想法。

function roundTo<T extends number | null | undefined>(num: T, decimals: number): T {
    if (num == null) return num;
    const factor = Math.pow(10, decimals);
    return (Math.round((num as number) * factor) / factor) as T;
  }

  function test(x: number, y: null, z: undefined, t?: number, u?: null) {
    let x1 = roundTo(x, 2); // -> number
    let x2 = roundTo(y, 2); // -> null
    let x3 = roundTo(z, 2); // -> undefined
    let x4 = roundTo(t, 2); // -> number | undefined
    let x5 = roundTo(u, 2); // -> null | undefined
  }

实际上,您可以摆脱一种类型强制 num as number 但可能不能摆脱另一种(至少不是一种简单的方法)

function roundTo<T extends number | null | undefined>(num: T, decimals: number): T {
    if (typeof num === 'number') {
      // here typescript knows, that num is specific subtype of T - number
      // but doesn't extend this to the T itself
      const factor = Math.pow(10, decimals);
      return Math.round(num * factor) / factor as T;
    }
    return num;
  }

如果您真的想强制 typescript 在函数内推断 T 的实际类型,可能是条件类型,但恕我直言,这太过分了。

@Johnny Oshika 的另一点 - 为了避免不必要的缩小数字 types 可以使用重载:

function roundTo(num: number, decimals: number): number;
  function roundTo<T extends number | null | undefined>(num: T, decimals: number): T;
  function roundTo<T extends number | null | undefined>(num: T, decimals: number): T {
   // function body
  }

回答2

这个答案归功于@jcalz。

使用重载签名设置条件返回类型 T extends number ? number : T。然后实现可以放宽类型推断。

// Overload signature
export function roundTo<T extends number | null | undefined>(
  num: T,
  decimals: number,
): T extends number ? number : T;

// Implementation
export function roundTo(
  num: number | null | undefined,
  decimals: number,
) {
  if (num === null || num === undefined) return num;
  const factor = Math.pow(10, decimals);
  return Math.round(num * factor) / factor;
}

例子:

roundTo(undefined, 2) // Returns type `undefined`
roundTo(null, 2) // Returns type `null`
roundTo(1.234 as number | undefined, 2) // Returns type `number | undefined`
roundTo(1.234 as number | null, 2) // Returns type `number | null`
roundTo(1.234, 2) // Returns type `number`

相似文章

随机推荐

最新文章