Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Typesafe string enums in TypeScript pre-2.4.

License

NotificationsYou must be signed in to change notification settings

dphilipson/typescript-string-enums

Repository files navigation

Typesafe string enums in TypeScript pre-2.4.

Build Status

As of TypeScript 2.4, this library is made mostly obsolete by native stringenums (see theannouncement).I recommend that most users who are on at least TypeScript 2.4 now use nativeenums instead. There are still a few minor reasons to continue to use thislibrary, as discussed in theAdvantages over native stringenums section.

Table of Contents

Installation

npm install --save typescript-string-enums

This library requires TypeScript 2.2 or later. If you require TypeScript 2.1compatibility, version 0.2.0 of this library is the last one with support.

Usage

Creating and using enums

Define an enum as follows:

// Status.tsimport{Enum}from"typescript-string-enums";exportconstStatus=Enum("RUNNING","STOPPED");exporttypeStatus=Enum<typeofStatus>;

Use it elsewhere:

import{Status}from"./Status";console.log(Status.RUNNING);// -> "RUNNING";// Works fine.constgoodStatus:Status=Status.RUNNING;// TypeScript error: Type '"hello"' is not assignable to type '"RUNNING" | "STOPPED"'constbadStatus:Status="hello";// Enum can be used for discriminated unions:typeState=RunningState|StoppedState;interfaceRunningState{    status:typeofStatus.RUNNING;    pid:number;}interfaceStoppedState{    status:typeofStatus.STOPPED;    shutdownTime:Date;}functionsaySomethingAboutState(state:State){// The following typechecks.if(state.status===Status.RUNNING){console.log("The pid is "+state.pid);}elseif(state.status===Status.STOPPED){console.log("The shutdown time is "+state.shutdownTime);}}

Instead of a list of values, an object may be passed instead if it is desiredthat the string values be different from the constant names. This also has theadvantage of allowing JSDoc comments to be specified on individual values. Forexample:

exportconstStatus=Enum({/**     * Hovering over Status.RUNNING in an IDE will show this comment.     */RUNNING:"running",STOPPED:"stopped",});exporttypeStatus=Enum<typeofStatus>;console.log(Status.RUNNING);// -> "running"

Additional functions

Enum.isType(enum, value)

Enum.isType checks if a value is of a given enum type and can be used as atype guard. For example:

constColor=Enum("BLACK","WHITE");typeColor=Enum<typeofColor>;letselectedColor:Color;constcolorString=getUserInputString();// Unsanitized string.// TypeScript error: Type 'string' is not assignable to type '"BLACK" | "WHITE"'.selectedColor=colorString;if(Enum.isType(Color,colorString)){// No error this time because within type guard.selectedColor=colorString;}else{thrownewError(`${colorString} is not a valid color`);}

Enum.keys(enum)

ResemblesObject.keys(), but provides strict typing in its return type.

Enum.values(enum)

ResemblesObject.values(), but provides strict typing in its return type.

Example of bothEnum.keys() andEnum.values():

constFileType=Enum({PDF:"application/pdf",Text:"text/plain",JPEG:"image/jpeg",});typeFileType=Enum<typeofFileType>;constkeys=Enum.keys(FileType);// Inferred type: ("PDF" | "Text" | "JPEG")[]// Return value: ["PDF", "Text", "JPEG"] (not necessarily in that order)constvalues=Enum.values(FileType);// Inferred type: ("application/pdf" | "text/plain" | "image/jpeg")[]// Return value: ["application/pdf", "text/plain", "image/jpeg"] (not necessarily in that order)

Enum.ofKeys(object)

Creates a new enum with the same keys as the provided enum or object and whosevalues are equal to its keys. This is most useful if for some reason it isnecessary to do string comparisons against the keys of an enum rather than thevalues. For example:

constErrorColor=Enum({OK:"green",ERROR:"red"});typeErrorColor=Enum<typeofErrorColor>;constErrorLevel=Enum.ofKeys(ErrorColor);consterrorLevel=getErrorLevel();if(errorLevel===ErrorLevel.ERROR){    ...}

Motivation

Enums are useful for cleanly specifying a type that can take one of a fewspecific values. TypeScript users typically implement enums in one of two ways:built-inTypeScriptenums or stringliterals, but each of these has drawbacks.

Why not built-in enums?

Built-in enums have one big drawback. Their runtime value is a number, which isannoying during development and makes them unsuitable for use with externalAPIs.

enumStatus{RUNNING,STOPPED}conststate={status:Status.RUNNING,pid:12345};console.log(state);// -> { status: 0, pid: 12345 }. What status was that again?// I hope you're not expecting other services to send you objects that look like this.

Why not string literals?

String literals make refactoring difficult. Suppose I have two enums:

typeStatus="RUNNING"|"STOPPED";typeTriathlonStage="SWIMMING"|"CYCLING"|"RUNNING";

Then if at a later stage I want to changeStatus to be"STARTED" | "STOPPED", there's no easy way to do it. I can't globally find/replace"RUNNING" to"STARTED" because it will also change the unrelated stringconstants representingTriathlonStage. Instead, I have to examine everyoccurrance of the string"RUNNING" to see if it needs to change. Besides,these kinds of global non-semantic substitutions should make you nervous.

Another disadvantage of string literals comes when using IDE autocompletefeatures. It's convenient to be able to typeStatus. and have autocompletesuggestStatus.RUNNING andStatus.STOPPED, but with string literals no suchsuggestion is possible.

I might try to solve both problems by introducing constants for the stringliterals, but this has issues as well:

// Typo on "STOPPED" not caught by anything below without additional boilerplate.typeStatus="RUNNING"|"STPOPED";// Naive attempts to define constants for these don't work, as shown below.constStatusNaive={RUNNING:"RUNNING",STOPPED:"STOPPED",};// Type error even though it shouldn't be, because StatusNaive.RUNNING has type// string which is not assignable to Status.conststatus:Status=StatusNaive.RUNNING;// Correctly defining constants is annoyingly repetitive. I shouldn't need to// write each value three times.constStatus={RUNNING:"RUNNING"as"RUNNING",STOPPED:"STOPPED"as"STOPPED",};

This library is effectively a programmatic version of these repetitivedefinitions. It attempts to provide the best of both worlds: string enums withthe convenience of built-in enums.

How It Works

This section is not necessary to use this library, but for those curious abouthow it is implemented, read on. The explanation uses the concepts of index typesand mapped types, as described in TypeScript'sAdvancedTypes page.

The relevant type declarations are as follows:

functionEnum<Vextendsstring>(...values: V[]):{[KinV]:K};function Enum<Textends{[_:string]:V},Vextendsstring>(definition: T): T;...type Enum<T> = T[keyof T];

We are creating a overloaded function namedEnum and a type namedEnum, soboth can be imported with a single symbol.

Consider the first overload, which handles the case of variadic argumentsrepresenting the enum values. In TypeScript, a string constant is a type (forexample, inconst foo = "Hello", the variablefoo is assigned type"Hello"). This means that the array

["RUNNING","STOPPED"]

can be inferred to have type("RUNNING" | "STOPPED")[], and so when it ispassed into a function with the above type signature, the type parameterV isthus inferred to be"RUNNING" | "STOPPED". Then the return type{ [K in V]: K } is a mapped type which describes an object whose keys are the types that makeupV and for each such key has a value of the same type as that key. Hence,the type ofEnum("RUNNING", "STOPPED") is

// This is a type, not an object literal.{    RUNNING:"RUNNING";    STOPPED:"STOPPED";}

Next, consider the second overload, which handles the case which takes an objectof keys and values, and for the sake of example consider

constStatus=Enum({RUNNING:"running",STOPPED:"stopped",});

The second type parameterV is inferred as"running" | "stopped", whichforces TypeScript to infer the first type parameterT as an object whosevalues are the specific string values that make upV. Hence, even though{ RUNNING: "running", "STOPPED": "stopped" } would have type{ RUNNING: string; STOPPED: string; }, passing it throughEnum causes its type to be inferredinstead as the desired

// Type, not object literal.{    RUNNING:"running";    STOPPED:"stopped";}

Next, consider the definition

typeEnum<T>=T[keyofT];

This is an index type which describes, for a given keyed typeT, the typeobtained by indexing intoT with an arbitrary one of its keys (the syntaxT[keyof T] is meant to evoke the expressiont[key] for somekey int).When passing in an arbitrary key to the object from the previous step, we get avalue which might be any one of the object's values, and so its type is thus theunion of the types of the object's values. Hence,Enum<typeof Enum("RUNNING", "STOPPED")> evaluates to"RUNNING" | "STOPPED", which is what we want.

Advantages over native string enums

With the addition of native string enums in TypeScript 2.4, this library will beunnecessary for most users. There are still a few niche reasons why users maystill prefer to use this library.

  • This library provides several helper functions which cannot easy beimplemented for native enums. Of these,Enum.isType() will likely be themost useful.
  • Defining a native string enum involves a bit of repetition, as each value mustbe written twice:
    enumColor{RED="RED",GREEN="GREEN",BLUE="BLUE"}
    vs
    constColor=Enum("RED","GREEN","BLUE");typeColor=Enum<typeofColor>;
    If there are many values, it may be desirable to avoid the repetition andcorresponding possibility for typos.

Acknowledgements

This libary is heavily inspired by posts inthisthread. In particular,credit goes to users@igrayson,@nahuel, and@kourge.

Copyright © 2017 David Philipson

About

Typesafe string enums in TypeScript pre-2.4.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors2

  •  
  •  

[8]ページ先頭

©2009-2025 Movatter.jp