Home > Mobile >  Deriving type from Readonly<Array<MyType>> does not work as expected
Deriving type from Readonly<Array<MyType>> does not work as expected

Time:11-25

TLDR; Deriving types from array not working as expected (Stackblitz to illustrate it all).

Trying to improve some parts of a codebase, I stumbled across several different arrays used to map info from our backend into human-readable strings - something like below:

export const MyMapping = [
  { dbLabel: 'something', screenLabel: 'Something User-friendly' },
  ...
];

As this exists in several places, but the "contract" of the array was not being enforced by some type, I went on and wrote this:

export type DbLabelMapper = Record<'dbLabel' | 'screenLabel', string>;

(actually the first version used an interface, but the idea was the same)

Then, as the database labels were being used as types in other parts of the codebase, but were wrongly used as strings, I went on and did as follows:

export const MyMapping: Array<DbLabelMapper> = [
  { dbLabel: 'something', screenLabel: 'Something User-friendly' },
  ...
] as const;
export type MyMappingType = typeof MyMapping[number]['dbLabel'];

Typescript screamed at me because I shouldn't assign a readonly type ([...] as const) to a mutable type (Array<DbLabelMapper>). So, I corrected the mapping signature as follows:

export const MyMapping: Readonly<Array<DbLabelMapper>> = [
  { dbLabel: 'something', screenLabel: 'Something User-friendly' },
  ...
] as const;
export type MyMappingType = typeof MyMapping[number]['dbLabel'];

Now, after that, my MyMappingType has no types whatsoever. According to my example, what I wanted was MyMappingType = 'something' | 'anotherThing' | '...'. Am I doing something wrong here, or did I miss something?

CodePudding user response:

You gave MyMapping an explicit type of Readonly<Array<DbLabelMapper>>. Using typeof MyMapping will return this exact type.

To fix this you will have to remove the type annotation and let TypeScript infer the type.

export const MyMapping = [
  { dbLabel: 'something', screenLabel: 'Something User-friendly' },
] as const

But you will lose the type safety you had before as you can assign any value to MyMapping now.

TypeScript 4.9 will introduce the satisfies operator to help with this situation.

export const MyMapping = [
  { dbLabel: 'something', screenLabel: 'Something User-friendly' },
] as const satisfies Readonly<Array<DbLabelMapper>>

export type MyMappingType = typeof MyMapping[number]['dbLabel'];
//          ^? type MyMappingType = "something"

Playground

  • Related