Is there an function to simplify the following union type TUnitTransform?

Viewed 96

Is there an function to simplify the type TUnitTransform as following:

type TUnitTransform = ((...rest: [number]) => [number]) &
  ((...rest: [number, number]) => [number, number]) &
  ((...rest: [number, number, number]) => [number, number, number]) &
  ......

Because there are some common functions that need to accept transformed parameters (unit "px" to "rpx"):

const fn1 = (rpx1: number) => { ... }
const fn2 = (rpx1: number, rpx2: number) => { ... }
const fn3 = (rpx1: number, rpx2: number, rpx3: number) => { ... }
......

And I have to transform them like that:

const px2rpx: TUnitTransform = (...rest: number[]): any => rest.map((i) => i / k)

fn1(...px2rpx(20))
fn2(...px2rpx(20, 30))
fn3(...px2rpx(20, 30, 40))
......
1 Answers

From that information I'd be inclined to rephrase TUnitTransform as:

type TUnitTransform = 
  <T extends [number, ...number[]]>(...rest: [...T]) => { [K in keyof T]: number; }

The parameter list of this function is constrained to be of type [number, ...number[]]; that's a tuple with a rest element and means that the list must start with a number and then contain zero or more numbers after that. This has the effect of requiring at least one parameter.

The output type of the function is a mapped array/tuple type which has the same length as the parameter list, and whose output type is all number. This transformation prevents the compiler from putting numeric literal types in the output. If I were to just say [...T], then a call to px2rx(20, 30, 40) would have the output of type [20, 30, 40], whereas we want [number, number, number].


And px2rx could be implemented like this:

const px2rpx: TUnitTransform = <T extends number[]>(...rest: T) =>
    rest.map(i => i / k) as { [K in keyof T]: number };

Note that TypeScript's standard library's type signature for Array.prototype.map() doesn't capture the preservation of array length, so I need the type assertion (with as) to avoid a compiler warning.


Let's see if it works:

const n1 = px2rpx(20);
// const n1: [number]

const n5 = px2rpx(1, 2, 3, 4, 5);
// const n5: [number, number, number, number, number]

// errors:

px2rpx(); // error!
// Expected at least 1 arguments, but got 0

px2rpx(1, "two", 3); // error!
// -----> ~~~~~
// string is not assignable to number

That all looks correct to me.

Playground link to code

Related