Home > Software design >  In Typescript, how to define a type based on an array's values and use the base array itself?
In Typescript, how to define a type based on an array's values and use the base array itself?

Time:04-22

I have an array of string values, and derive a type from the array.

I want to also be able to use the base array itself to reference all the possible values.

The Typescript code is similar to this (in the real code there are more possible values but the rest is the same) :

const ALL_COLORS = [ 'red', 'green', 'blue' ] as const;
type Color = typeof ALL_COLORS[number];

function acceptColors (colors: Color[]) {
  // do something with the colors
}

acceptColors(ALL_COLORS);

The type definition is fine:

enter image description here

But the last line triggers the following type error:

TS2345: Argument of type 'readonly ["red", "green", "blue"]' is not assignable to parameter of type '("red" | "green" | "blue")[]'.   The type 'readonly ["red", "green", "blue"]' is 'readonly' and cannot be assigned to the mutable type '("red" | "green" | "blue")[]'.

I can force the type checker to accept it with:

acceptColors(ALL_COLORS as unknown as Color[]);

But it's not nice at all.

I can't say I really understand what the problem is here. I'm confused by the concept of "readonly" vs "mutable" types. Since types are build-time only, how can they be mutable?

Can you explain the problem and is there a way to achieve what I want, without ugly type assertions, or repeating all the possible values?

CodePudding user response:

TypeScript is warning you that the type definition of the argument does not specify that the array should not be mutated. That is, you could do

function acceptColors (colors: Color[]) {
  [colors[1], colors[2]] = [colors[2], colors[1]];
}

and then:

acceptColors(ALL_COLORS);
// but now the ALL_COLORS is ['red', 'blue', 'green']
// and not a tuple of ['red', 'green', 'blue'] as it was originally
// so if something refers to, eg ALL_COLORS[1] after this
// the type will incorrectly be 'green' instead of 'blue'

You need to specify that the array is readonly, so that it can't be changed inside acceptColors.

function acceptColors (colors: readonly Color[]) {
  // do something with the colors
}

CodePudding user response:

You can try removing readonly modifier from the Colortype using mapped type alias And be able to assign the Color[] to acceptColors

type ArrayToUnion<T extends string[]> = T[number]

type RemoveReadOnly<T> = { -readonly [K in keyof T]: T[K]  }

const ALL_COLORS = [ 'red', 'green', 'blue' ] as const

type Color = ArrayToUnion<RemoveReadOnly<typeof ALL_COLORS>>;

function acceptColors (colors: Color[]) {
  // do something with the colors
}

acceptColors(["red", "blue", "green"]);
  • Related