Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

String Literal Types with variables in Typescript

Tags:

typescript

my codes:

export const LOAD_USERS = 'LOAD_USERS';
export const CREATE_USER = 'CREATE_USER';
export interface ACTION {
  type: string,
  payload: any
}

I want to restrict the ACTION.type to be either 'LOAD_USERS' or 'CREATE_USERS'. I can do this by String Literal type: 'LOAD_USERS'|'CREATE_USERS'. But can not do it with variables type: LOAD_USERS | CREATE_USERS. My editor prompts "can not find name 'LOAD_USERS'". Is there a way to use variables to do this? There could be typo when typing the same string in more than one place.

like image 630
Timathon Avatar asked Apr 08 '16 11:04

Timathon


People also ask

How do you use string literal in TypeScript?

Hence, you can treat a variable that has a string literal type like a variable of type string . You can access properties, call methods, and use operators, just as you would with regular strings: const eventName: "click" | "mouseover" = "click"; eventName. length; // 5 eventName.

Is a string literal or variable?

A string literal is where you specify the contents of a string in a program. Here 'A string' is a string literal. The variable a is a string variable, or, better put in Python, a variable that points to a string.

What are examples of string literals?

A string literal is a sequence of zero or more characters enclosed within single quotation marks. The following are examples of string literals: 'Hello, world!' 'He said, "Take it or leave it."'

How many types of string literals are there?

There are three sets of literal types available in TypeScript today: strings, numbers, and booleans; by using literal types you can allow an exact value which a string, number, or boolean must have.


5 Answers

You can use the typeof operator, it returns the inferred type:

export const LOAD_USERS = 'LOAD_USERS';
export const CREATE_USER = 'CREATE_USER';
export interface ACTION {
  type: typeof LOAD_USERS | typeof CREATE_USER,
  payload: any
}
like image 79
despairblue Avatar answered Oct 05 '22 03:10

despairblue


If you want to ensure that the string in your variables will be the action type, then you should use a type alias and explicitly type the variables with that type:

export type ActionNames = 'LOAD_USERS' | 'CREATE_USER';
export const LOAD_USERS: ActionNames = 'LOAD_USERS';
export const CREATE_USER: ActionNames = 'CREATE_USER';

export interface ACTION {
  type: ActionNames;
  payload: any;
}

If the strings in the variables don't match one of the strings in ActionTypes, then you'll get an error, which is desired to prevent mistakes. For example, this would error:

export type ActionNames = 'LOAD_USERS' | 'CREATE_USER';
export const LOAD_USERS: ActionNames = 'LOAD_USERS_TYPO'; // error, good

Update

Note that in newer versions of TypeScript the following is another option:

const actionNames = ['LOAD_USERS', 'CREATE_USER'] as const;
type ActionNames = typeof actionNames[number]; // typed as 'LOAD_USERS' | 'CREATE_USER'

Also, looking back on this question, you probably want to declare your actions with a common string literal type property that's differentiated by the string literal type (see discriminated unions).

For example:

interface LoadUsersAction {
    type: "LOAD_USERS";
}

interface CreateUserAction {
    type: "CREATE_USER";
    name: string;
    // etc...
}

type Actions = LoadUsersAction | CreateUserAction;

Also, I recommend not bothering with the variables. Using the strings directly is type safe.

like image 39
David Sherret Avatar answered Oct 05 '22 01:10

David Sherret


Since TypeScript 2.4 you can use enums with string members. Enums define a set of named constants.

export enum UserActions{
    LOAD_USERS = "LOAD_USERS",
    CREATE_USER = "CREATE_USER"
}

export interface ACTION {
    type: UserActions,
    payload: any
}
like image 42
Benjamin Pajk Avatar answered Oct 05 '22 01:10

Benjamin Pajk


You cannot totally eliminate the duplication, but you can reduce it to the bare minimum:

type nameIt = 'LOAD_USERS' | 'CREATE_USERS'
export const LOAD_USERS = 'LOAD_USERS';
export const CREATE_USER = 'CREATE_USER';

This allows you to use the nameIt type elsewhere, but it does mean repeating the strings in your constants and in the type.

Alternatively, if you are writing a new piece of code, you might prefer to use an enum.

like image 21
Fenton Avatar answered Oct 05 '22 03:10

Fenton


Simplifying the existing answers further,

export type ActionTypes = 'LOAD_USERS' | 'CREATE_USER';

is sufficient by itself. To refer to a specific action type, just use the quoted string literal:

'LOAD_USERS'

In some other languages we avoid repeating such string literals and prefer to name them with a declaration. But that's because those other languages don't have string literal types! In TypeScript, 'LOAD_USERS' already is a compile-time name for a statically-checkable type. You don't necessarily need to give it another name.

Example:

declare function doAction(action: ActionTypes);

doAction('LOAD_USERS'); // okay

doAction('LOAD_USES'); // error: typo is caught by compiler
like image 21
Daniel Earwicker Avatar answered Oct 05 '22 02:10

Daniel Earwicker