Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define a type based on values of an array?

If I have a type which looks like an array:

type names = ['Mike', 'Jeff', 'Ben'];

I can easily define another type which has values of items in names:

type UserName = names[number]

For a function:

function hello(name: UserName) {
  console.log(`Hello, ${name}!`)
}

I can only pass one of Mike, Jeff, Ben to function hello. If I give other values, like John, it can't compile.

What if I don't have a type names, but a const array names?

const names = ['Mike', 'Jeff', 'Ben'];

type UserName = ???;

function hello(name: UserName) {
  console.log(`Hello, ${name}!`)
}

hello('Mike');

Is it possible to define such a type UserName?

like image 782
Freewind Avatar asked Feb 09 '19 15:02

Freewind


People also ask

How do you define an array type?

Summary. In TypeScript, an array is an ordered list of values. An array can store a mixed type of values. To declare an array of a specific type, you use the let arr: type[] syntax.

Can you declare the type of the array?

We identify the data type of the array elements, and the name of the variable, while adding rectangular brackets [] to denote its an array. Here are two valid ways to declare an array: int intArray[]; int[] intArray; The second option is oftentimes preferred, as it more clearly denotes of which type intArray is.

Can the values of an array be any data type?

You can create an array with elements of different data types when declare the array as Object. Since System. Object is the base class of all other types, an item in an array of Objects can have a reference to any other type of object.

How do you define values in an array?

There are several ways to declare and int array: int[] i = new int[capacity]; int[] i = new int[] {value1, value2, value3, etc}; int[] i = {value1, value2, value3, etc};


2 Answers

In TypeScript 3.4, which should be released in March 2019 it will be possible to tell the compiler to infer the type of a tuple of string literals as a tuple of string literals, instead of as string[], by using the as const syntax. It should look like this:

const names = ['Mike', 'Jeff', 'Ben'] as const; // TS3.4 syntax
type Names = typeof names; // type Names = readonly ['Mike', 'Jeff', 'Ben'] 
type UserName = Names[number]; // 'Mike' | 'Jeff' | 'Ben'

Until then (in TypeScript 3.0 through 3.3) you can get this effect by using a helper function which gives the compiler hints to infer a narrower type:

type Narrowable = string | number | boolean | undefined | null | void | {};
const tuple = <T extends Narrowable[]>(...t: T)=> t;
const names = tuple('Mike', 'Jeff', 'Ben');

type Names = typeof names; // type Names = ['Mike', 'Jeff', 'Ben'] 
type UserName = Names[number]; // 'Mike' | 'Jeff' | 'Ben'

(Note that in both cases you can skip the intermediate Names type and just define type UserName = (typeof names)[number] if you prefer)

Okay, hope that helps. Good luck!

like image 102
jcalz Avatar answered Nov 04 '22 00:11

jcalz


Again, one should most of the time:

  1. declare types
  2. set those types to variables

exactly in that order.

Rarely one should do the opposite thing.

If you really need it though, you can do it like this:

const names = ['Mike', 'Jeff', 'Ben'] as ['Mike', 'Jeff', 'Ben'];

type UserName = typeof names;

because you want a tuple type (['Mike', 'Jeff', 'Ben']), but arrays are never inferred as tuples by default, but only as arrays (string[] in this case). However I don't see much sense in doing above thing and again I am suggesting you to do the opposite, idiomatic thing:

type UserName = ['Mike', 'Jeff', 'Ben'];
// however the above type is absolutely static 
// and I don't know if it can provide any benefit so maybe this is more correct:
type UserName = ('Mike' | 'Jeff' | 'Ben')[]

const names: UserName = ['Mike', 'Jeff', 'Ben'] // ok
like image 22
Nurbol Alpysbayev Avatar answered Nov 03 '22 23:11

Nurbol Alpysbayev