Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ways to get string literal type of array values without enum overhead

Tags:

typescript

In my project I need a number of string literal types and arrays of allowed values for variuos tasks like typeguards.

This is what we have:

type Animal = 'cat' | 'dog' | 'rabbit' | 'snake'
const animals: Animal[] = ['cat', 'dog', 'rabbit', 'snake']

It works, but it requires to list keys several times in source code.

Another way is to use enums as suggested in Get array of string literal type values but it creates overhead in runtime as compiled code for enum is bigger then array.

I found another way to do it that have no runtime overhead, but I'm not sure if it will work in the future, as it may be a bug.

const notWidened = <T extends string>(val: T[]) => val

const animals = notWidened(['cat', 'dog', 'rabbit', 'snake'])
type Animal = typeof animals[0]

So the question is if it's safe to use this snippet or it is going to break in the future. Is there a better way to get both literal string type and array without duplication.

like image 908
Vladimir Danchenkov Avatar asked Sep 12 '17 12:09

Vladimir Danchenkov


People also ask

What is a literal enum in Python?

Literal enum members: Literal enum types are enum types where each member either has no initializer, or an initializer that specifies a numeric literal, a string literal, or an identifier naming another member in the enum type. Values are initialized implicitly via number literals, or explicitly via string literals

What are the advantages of string literals over enums?

A combination of string literals and union types offers as much safety as enums, and has the advantage of translating more directly to JavaScript. It also offers similarly strong autocomplete in various IDEs. If you’re curious, you can quickly check this on the TypeScript Playground.

Are string literals allowed with enums in typescript?

This means that literal strings (even if they match the values of enum members) would be considered invalid. To illustrate this, let's look at the following example: As you can see in the example above, string literals are not allowed with enums. This is the intended behavior of enums in TypeScript because:

Can a string enum have a union type?

For a string enum, we can produce a union type out of the literal types of its values, but it doesn’t happen the other way. String-based enums were only introduced to TypeScript in version 2.4, and they made it possible to assign string values to an enum member. Let us look at an example from the documentation below:


2 Answers

I can't find a good piece of documentation for this at the moment, but your notWidened() function works just fine and is not a bug. TypeScript will infer string literals for generic type variables if you constrain the type parameter to string or a subtype of string. So <T extends keyof U>, <T extends string>, <T extends 'a'|'b'>, etc. will infer string literals for T. (You can also infer number or boolean literals if you constrain T similarly).

So your code is just fine as far as I can see; the only thing I might do differently is

type Animal = (typeof animals)[number]

instead of

type Animal = typeof animals[0]

since the 0th element of animals is actually 'cat', even though you've told TypeScript it is 'cat'|'dog'|.... Yours is fine, though.

As I commented above, if you want TypeScript to consider animals to be a tuple where animals[0] is of type 'cat', and animals[1] is of type 'dog', etc., you can use something like the function tuple() in tuple.ts (UPDATE July 2018, starting in TypeScript 3.0 the compiler will be able to infer tuple types automatically, so the function can be more succinct):

export type Lit = string | number | boolean | undefined | null | void | {};
export const tuple = <T extends Lit[]>(...args: T) => args;

const animals = tuple('cat', 'dog', 'rabbit', 'snake');
type Animal = (typeof animals)[number];  // union type

which might come in handy for you.


TL;DR: đź‘Ť your code is fine.

Hope that helps; good luck!

like image 73
jcalz Avatar answered Oct 18 '22 02:10

jcalz


Maybe you can use a const enum string, this has none runtime overhead. It compiles the values into the code (Playground):

const enum Animals {
  Cat = 'cat',
  Dog = 'dog',
  Rabbit = 'rabbit',
  Snake = 'snake'
}

alert(Animals.Cat)

// Generated JavaScript code: 
// alert("cat" /* Cat */);
like image 35
Markus Avatar answered Oct 18 '22 01:10

Markus