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