How can I add aliases to an array of numbers in typescript

131 Views Asked by At

I'm trying to add some aliases to an array of numbers but typescript wont accept it, here is the code:

  interface FontSizeAliases {
  micro: number
  small: number
  default: number
  large: number
}


const fontSizes: number[] & FontSizeAliases = [12, 14, 16, 18, 22, 30, 40]
fontSizes.micro = fontSizes[0]
fontSizes.small = fontSizes[1]
fontSizes.default = fontSizes[2]
fontSizes.large = fontSizes[3]

compiler error:

Type 'number[]' is not assignable to type 'number[] & FontSizeAliases'.
  Type 'number[]' is missing the following properties from type 'FontSizeAliases': micro, small, default, large
3

There are 3 best solutions below

0
On BEST ANSWER

The problem is that your array doesn't have micro etc. when you assign it to the fontSizes constant, but that constant's type requires it.

Object.assign is often used to build up things like that (but keep reading, I work up to a third option which I think is probably best):

interface FontSizeAliases {
    micro: number;
    small: number;
    default: number;
    large: number;
}

const rawSizes = [12, 14, 16, 18, 22, 30, 40];
const fontSizes: number[] & FontSizeAliases = Object.assign(rawSizes, {
    micro: rawSizes[0],
    small: rawSizes[1],
    default: rawSizes[2],
    large: rawSizes[3],
});

Playground link

Another option is to build them up in a Partial<FontSizeAliases>:

interface FontSizeAliases {
    micro: number;
    small: number;
    default: number;
    large: number;
}

const rawSizes: number[] & Partial<FontSizeAliases> = [12, 14, 16, 18, 22, 30, 40];
rawSizes.micro = rawSizes[0];
rawSizes.small = rawSizes[1];
rawSizes.default = rawSizes[2];
rawSizes.large = rawSizes[3];
const fontSizes = rawSizes as number[] & FontSizeAliases;

Playground link

In both of those, though, you have the issue that you could miss out one of the sizes (small, for instance). To fix that, you can create the sizes separately with full typechecking, then combine them:

interface FontSizeAliases {
    micro: number;
    small: number;
    default: number;
    large: number;
}

const rawSizes = [12, 14, 16, 18, 22, 30, 40];
const sizeNames: FontSizeAliases = {
    micro: rawSizes[0],
    small: rawSizes[1],
    default: rawSizes[2],
    large: rawSizes[3],
};
const fontSizes = Object.assign([], rawSizes, sizeNames);

Playground link

3
On

I would suggest to slightly modify your types, like this:

interface FontSizeAliases {
    micro?: number
    small?: number
    default?: number
    large?: number
}

const fontSizes: number[] & FontSizeAliases = [12, 14, 16, 18, 22, 30, 40]

fontSizes.micro = fontSizes[0]
fontSizes.small = fontSizes[1]
fontSizes.default = fontSizes[2]
fontSizes.large = fontSizes[3]

I've made micro optional, this will give you intellisense and make sure you will not forget to check undefined

Link to the playground

0
On

I wanted to reduce boilerplate by making a utility function. It does have a @ts-ignore, but you can reuse it without errors.

This version lets you specify every index manually:

const keyedArray = <T, U extends string>(
  values: T[],
  names: { [k in U]: number }
): T[] & { [k in U]: T } => {
  // @ts-ignore
  return Object.assign(
    [...values],
    Object.entries<number>(names).reduce(
      (acc, [name, index]) => ({
        ...acc,
        [name]: values[index],
      }),
      {}
    )
  );
};

const fontSizes = keyedArray([0, 2, 16], { sm: 0, lg: 1, xl: 2 });
/*
const fontSizes: number[] & {
    sm: number;
    lg: number;
    xl: number;
}
*/