TypeScript:returns 与参数类型相同的通用函数
TypeScript: Generic function which returns the same type as parameter
考虑这个(不编译):
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);
}
我想要 return 传递给 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
的 return 类型在编译时是已知的,因此希望能够根据第一个参数中传递的类型从那里继承类型。
我可以通过转换 return 类型 as any
来进行编译,但这会破坏类型安全。我也可以通过使用 extends
而不是 =
并使用 return 类型 as T
进行编译,但它具有 returning [=] 的不良行为19=] 当传入 null
或 undefined
时。
如何让 TypeScript 表现出所需的行为?
相关:
应该是T extends ...
,不是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
}
实际上,您可以摆脱一种类型的强制转换 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;
}
如果您真的想要强制打字稿推断函数内部 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
}
此答案归功于@jcalz。
使用重载签名设置 T extends number ? number : T
的条件 return 类型。然后实现可以放宽类型推断。
// 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`
考虑这个(不编译):
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);
}
我想要 return 传递给 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
的 return 类型在编译时是已知的,因此希望能够根据第一个参数中传递的类型从那里继承类型。
我可以通过转换 return 类型 as any
来进行编译,但这会破坏类型安全。我也可以通过使用 extends
而不是 =
并使用 return 类型 as T
进行编译,但它具有 returning [=] 的不良行为19=] 当传入 null
或 undefined
时。
如何让 TypeScript 表现出所需的行为?
相关:
应该是T extends ...
,不是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
}
实际上,您可以摆脱一种类型的强制转换 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;
}
如果您真的想要强制打字稿推断函数内部 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
}
此答案归功于@jcalz。
使用重载签名设置 T extends number ? number : T
的条件 return 类型。然后实现可以放宽类型推断。
// 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`