打字稿:返回与参数相同类型的通用函数

发布于 2025-01-30 14:59:17 字数 1090 浏览 3 评论 0原文

考虑一下(不编译):

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

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

我可以通过将返回类型施放为任何来进行编译,但这会破坏类型的安全性。我还可以通过使用扩展而不是=并将返回类型作为t 进行编译,但是它具有返回<< 代码>任何当null未定义被传递时。

如何获得打字稿以展示所需的行为?

有关的: https://stackoverflow.com/a/a/51195834/188740 https://stackoverflow.com/a/a/57529925/188740

Consider this (which doesn't compile):

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

I would like to return the same type that's passed in to the roundTo() function.

For example:

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

The return type of roundTo is known at compile time, so the desire is to be able to carry the type forward from there based on the type passed in the first parameter.

I can make this compile by casting the return type as any, but that would break type safety. I can also make this compile by using extends instead of = and casing the return type as T, but it has the undesired behavior of returning any when null or undefined is passed in.

How can I get TypeScript to exhibit the desired behavior?

Related:
https://stackoverflow.com/a/51195834/188740
https://stackoverflow.com/a/57529925/188740

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

贱人配狗天长地久 2025-02-06 14:59:17

它应该是t扩展...,而不是t = ...。后一种形式是“ t的默认值”,它不是被推断出来的,而是总是被称为声明的,基本上杀死了整个想法。

  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
  }

实际上,您可以摆脱一种类型的coersion num为号码,但

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

如果您真正地想强制推断实际类型在功能内部的t,可能是有条件的键入方式,但恕我直言,这是过度的。

@johnny oshika的另一点 - 可以避免使用不必要的数字类型的狭窄,可以使用:

  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
  }

It should be T extends ..., not T = .... The latter form is "default value for T", and it is not inferred but rather always taken as declared, basically killing entire idea.

  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
  }

Actually, you can get rid of one type coersion num as number but probably not the other (at least not an easy way)

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

If you really want to force typescript infer actual type of T inside the function, probably conditional typings are the way, but imho it's overkill.

One more point from @Johnny Oshika - to avoid unwanted narrowing of number types overloads can be used:

  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
  }
泪意 2025-02-06 14:59:17

此答案归功于@jcalz。

使用过载签名设置有条件的返回类型的t扩展数字?编号: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`

Credit for this answer goes to @jcalz.

Use an overload signature to set a conditional return type of T extends number ? number : T. Then the implementation can relax the type inference.

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

Examples:

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`
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文