Template Literal Types
Sixian Li
Quick Refresher
Type is a set of values
Type
Type | Values | Size |
---|---|---|
boolean | true, false | 2 |
string | "a", "b", "Ollie"... | ∞ |
"foo" | "foo" | 1 |
string[] | ["1"], ["2", "Kickflip"] | ∞ |
"foo" | "bar" | "foo", "bar" | 2 |
never | (no value, similar to ∅) | 0 |
extends
A extends B
⇨ A is assignable to B
⇨ Every value in the set A is assignable to a variable of type B
⇨ B can be replaced with any value in the set A without a problem
Conditional Types
When the type on the left of the extends is assignable to the one on the right, then you’ll get the “true” branch; otherwise you’ll get the “false” branch.
SomeType extends OtherType ? TrueType : FalseType;
interface Animal {
sleep(): void;
}
interface Dog extends Animal {
woof(): void;
}
type Example1 = Dog extends Animal ? number : string; // number
type Example2 = RegExp extends Animal ? number : string; // string
Template Literal Types
![](https://media2.giphy.com/media/9Y6n9TR7U07ew/giphy.gif)
Syntax
JavaScript template literal string
const a = `string text ${expression} string text`
TypeScript template literal types
type World = "world";
type Greeting = `hello ${World}`;
= "hello world"
Union
When a union is used in the interpolated position, the result is distributed.
"You fill the gap with each of the value."
type World = "world" | "tomorrow";
type Greeting = `hello ${World}`;
= "hello world" | "hello tomorrow"
TemplateLiteral<A | B | C> =
TemplateLiteral<A> | TemplateLiteral<B> | TemplateLiteral<C>
type Colors = "black" | "white" | "purple"
type Animals = "goose" | "hamster"
type ColoredAnimals = `${Colors} ${Animals}`
Multiple interpolated positions
Cartesian Product
Color/Animal | goose | hamster |
---|---|---|
black | ||
white | ||
purple |
Multiple interpolated positions
Color/Animal | goose | hamster |
---|---|---|
black | (black, goose) | (black, hamster) |
white | ||
purple |
Multiple interpolated positions
Color/Animal | goose | hamster |
---|---|---|
black | (black, goose) | (black, hamster) |
white | (white, goose) | (white, hamster) |
purple | (purple, goose) | (purple, hamster) |
Multiple interpolated positions
Color/Animal | goose | hamster |
---|---|---|
black | (black, goose) | (black, hamster) |
white | (white, goose) | (white, hamster) |
purple | (purple, goose) | (purple, hamster) |
type Colors = "black" | "white" | "purple"
type Animals = "goose" | "hamster"
type ColoredAnimals = `${Colors} ${Animals}`
= "black goose" | "black hamster" | "white goose" | "white hamster" | "purple goose" | "purple hamster"
Multiple interpolated positions
Destructuring
`infer`
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
type EverythingBeforeWorld<T> = T extends `${infer H}World` ? H : never;
type T1 = EverythingBeforeWorld<"Hello World">;
= "Hello "
Inferred type variables may be referenced in the true branch of the conditional type
Match on the structure of the string
Examples
![](https://media4.giphy.com/media/Ll88bcCbnV5U5UGsW7/giphy.gif)
1. logEngagement
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2045103/images/9532084/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2045103/images/9544166/pasted-from-clipboard.png)
1. logEngagement
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2045103/images/9532085/pasted-from-clipboard.png)
We want to ensure there is no "." in area, scenario and operation strings
1. logEngagement
function logEngagement<TScenario extends string, TOperation extends string>(
area: EngagementLogArea,
scenario: NoDotString<TScenario>,
operation: NoDotString<TOperation>
): void {
console.log(`${prefix}.${area}.${scenario}.${operation}`)
}
type NoDotString<S extends string> = S extends `${any}.${any}` ? never : S
2. Smart Split
type TopicSource = "User" | "VivaTopics" | "Yammer";
// Response from API
// "User,VivaTopics" | "User,Yammer" | "VivaTopics,User" | ...
2. Smart Split
type TopicSource = "User" | "VivaTopics" | "Yammer";
// Response from API
// "User,VivaTopics" | "User,Yammer" | "VivaTopics,User" | ...
type Res = `${TopicSource},${TopicSource}`;
2. Smart Split
type TopicSource = "User" | "VivaTopics" | "Yammer";
// Response from API
// "User,VivaTopics" | "User,Yammer" | "VivaTopics,User" | ...
type Res = `${TopicSource},${TopicSource}`;
declare const res: Res;
declare let source: TopicSource;
// Q1: What is the type of a?
const a = res.split(",");
// Q2: Does this work?
source = a[0];
type TopicSource = "User" | "VivaTopics" | "Yammer";
// e.g., 'User,VivaTopics' | 'VivaTopics,Yammer'
type Res = `${TopicSource},${TopicSource}`;
declare const res: Res;
// Q: What is the type of a?
// A: string[]
const a = res.split(",");
type TopicSource = "User" | "VivaTopics" | "Yammer";
// Response from API
// "User,VivaTopics" | "User,Yammer" | "VivaTopics,User" | ...
type Res = `${TopicSource},${TopicSource}`;
declare const res: Res;
declare let source: TopicSource;
// Q1: What is the type of a?
// A1: string[]
const a = res.split(",");
// Q2: Does this work?
source = a[0];
2. Smart Split
type TopicSource = "User" | "VivaTopics" | "Yammer";
// e.g., 'User,VivaTopics' | 'VivaTopics,Yammer'
type Res = `${TopicSource},${TopicSource}`;
declare const res: Res;
// Q: What is the type of a?
// A: string[]
const a = res.split(",");
type TopicSource = "User" | "VivaTopics" | "Yammer";
type Res = `${TopicSource},${TopicSource}`;
declare const res: Res;
declare let source: TopicSource;
// Q1: What is the type of a?
// A1: string[]
const a = res.split(",");
// Q2: Does this work?
// A2: No
source = a[0];
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2045103/images/9544394/pasted-from-clipboard.png)
2. Smart Split
split(separator: string | RegExp, limit?: number): string[];
⬆️ ☹️
type TopicSource = "User" | "VivaTopics" | "Yammer";
type Res = `${TopicSource},${TopicSource}`;
declare const res: Res;
declare let source: TopicSource;
// Q1: What is the type of a?
// A1: string[]
const a = res.split(",");
// Q2: Does this work?
// A2: No
source = a[0];
2. Smart Split
![](https://media4.giphy.com/media/4EALRFjyD5odO/giphy.gif)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2045103/images/9544711/pasted-from-clipboard.png)
2. Smart Split
interface String {
smartSplit<TString extends string, TSep extends string>(
this: TString,
sep: TSep
): Split<TString, TSep>;
}
type Split<
TString extends string,
TSep extends string
> = TString extends `${infer L}${TSep}${infer R}`
? [L, ...Split<R, TSep>]
: [TString];
type SplitTailRecursive<
TString extends string,
TSep extends string,
Acc extends string[] = []
> = TString extends `${infer L}${TSep}${infer R}`
? SplitTailRecursive<R, TSep, [...Acc, L]>
: Acc;
2. Smart Split
3. querySelector
document.querySelector('div#app') // ==> HTMLDivElement
document.querySelector('div#app > form#login') // ==> HTMLFormElement
document.querySelectorAll('span.badge') // ==> NodeListOf<HTMLSpanElement>
anElement.querySelector('button#submit') // ==> HTMLButtonElement
3. querySelector
// lib.dom.d.ts
/** Returns the first element that is a descendant of node that matches selectors. */
querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;
querySelector<K extends keyof SVGElementTagNameMap>(selectors: K): SVGElementTagNameMap[K] | null;
querySelector<E extends Element = Element>(selectors: string): E | null;
const a = document.querySelector('div') // HTMLDivElement
const b = document.querySelector('div > button') // Element
const c = document.querySelector(' ') // Element, but run-time error
3. querySelector
type WhiteSpace = " " | "\n" | "\t" | "\r" | "\f";
type Trim<S extends string> = S extends `${WhiteSpace}${infer Rest}`
? Trim<Rest>
: S extends `${infer Rest}${WhiteSpace}`
? Trim<Rest>
: S;
type Combinator = " " | "+" | "~" | ">";
type GetLastElementTag<TSelector> =
TSelector extends `${infer _}${Combinator}${infer Rest}`
? GetLastElementTag<Rest>
: TSelector;
type T1 = GetLastElementTag<'div > button'> // ==> "button"
type IsInvalid<TSelector extends string> = Trim<TSelector> extends '' | `${any}${Combinator}` ? true : false
type Invalid1 = IsInvalid<'div'> // ==> false
type Invalid2 = IsInvalid<'div >'> // ==> true
type TagNameToElement<TTagName> = TTagName extends keyof HTMLElementTagNameMap
? HTMLElementTagNameMap[TTagName]
: never;
type QuerySelector<TSelector extends string> = IsInvalid<TSelector> extends true ? never : TagNameToElement<GetLastElementTag<TSelector>>
type Q1 = QuerySelector<'div > button ~ li'> // ==> HTMLLIElement
References
THANK YOU
![](https://media2.giphy.com/media/l0MYSOnaOKyWPTf0c/giphy.gif)
Template Literal Types
By Sixian Li
Template Literal Types
- 212