What are Template Literal Types?

Justin Lee
3 min readAug 8, 2021

After TypeScript 4.1 came out, we now can use the “Template Literal Type”. There are so many awesome features in it. In this article, I will highlight some features, but it is very recommended to read the official docs.

Template Literal Type?

It is making a new type from the string literal type of TypeScript.

  • Basic
type Justin = 'justin';type JustinLee = '${Justin} Lee';

We now can combine string and type to make a new type.

  • Union Type
type Banana = 'banana';
type Toppings = 'chocalate' | 'strawberries' | 'blueberries' | 'syrup';
//type BananaToppings = 'banana chocolate' | 'banana strawberries' | 'banana blueberries' | 'banana syrup';
type BananaToppings = `${Banana} ${Toppings}`;

You can simply link types, you can extend your type to various ways.

  • Union Types
type VerticalAlignment = "top" | "middle" | "bottom";
type HorizontalAlignment = "left" | "center" | "right";
// type Alignments =
// | "top-left" | "top-center" | "top-right"
// | "middle-left" | "middle-center" | "middle-right"
// | "bottom-left" | "bottom-center" | "bottom-right"
type Alignments = `${VerticalAlignment}-${HorizontalAlignment}`;
  • Key Remapping in Mapped Types
type Options = {
[K in "isLoading" | "hasElements" | "isActive"]?: boolean;
};
// same as
// type Options = {
// isLoading?: boolean,
// hasElements?: boolean,
// isActive?: boolean
// };
  • Capitalize<type>
type Get<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
interface User {
name: string;
age: number;
location: string;
}
type CapitalizedUser = Get<User>;
// type CapitalizedUser = {
// getName: () => string;
// getAge: () => number;
// getLocation: () => string;
//}
  • Interfacing type
//Simple literal typestype InOrOut<T> = T extends `fade${infer R}` ? R : never;//type I = "In"
type I = InOrOut<"fadeIn">;
//type O = "Out"
type O = InOrOut<"fadeOut">;

//Split literal with '.'
type Split<S extends string> =
S extends `${infer T}.${infer U}` ? [T, ...Split<U>] : [S];
// type S = ["foo", "bar", "baz"];
type S = Split<"foo.bar.baz">;
  • ValueOf (it’s not from 4.1 but just to keep in mind)
interface Foo {
foo: {
bar: {
baz: string;
}
}
}
//type A = { bar: { baz: string} };
type A = ValueOf<Foo, ['foo']>;
//type B = { baz : string };
type B = ValueOf<Foo, ['foo', 'bar']>;
//type C = string;
type C = ValueOf<Foo, ['foo', 'bar', 'baz']>;

Ex1) Make JSON parser with TypeScript

// type Json = { key1: ['value1', null]; key2: 'value2' }; 
type Json = ParseJson<'{ "key1": ["value1", null], "key2": "value2" }'>;

As you can see you can just insert your JSON to ParseJson type.

Ex2) Make snake_case type to camelCase type

type CamelizeString<T extends PropertyKey> = 
T extends string ? string extends T ? string :
T extends `${infer F}_${infer R}` ? `${F}${Capitalize<CamelizeString<R>>}` : T : T;

type Camelize<T> = { [K in keyof T as CamelizeString<K>]: T[K] }

type CamelizeExample = Camelize<Example>;
/* type CamelizeExample = {
firstName: string;
lastName: string;
homeTown: string;
} */

const e: Camelize<Example> = {
firstName: 'string',
lastName: 'string',
homeTown: 'string'
}

Awesome, Right?

References

--

--