考虑一下(不编译):
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
来进行编译,但是当传入 null
或 undefined
时,它具有返回 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`