But the category of our types can be very specific, so specific they represent a single thing.
let aSpecificNumber: 123 = 123
aSpecificNumber = 456// fails to compile - 456 is not 123type UniversalPair = ['secret', 42]
let truth: UniversalPair = ['s3cr3t', 42] // 's3cr3t' is not 'secret'
Literal types (const.)
Often, defining constants sets the type of the constant as a literal type.
TypeScript 4.1 added template literals allowing us to widen literal types to allow partial matching.
let someAbstractQuantity: `${string}ful` = 'mouthful'let invalidQuantity: `${string}ful` = 'mouth'// mouth not assignable to ${string}fullet ski: `ski${string}` = 'ski-ball'let numericalQuantity: `exactly-${number}-hens` = 'exactly-3-hens'let alsoNumericalQuantity: `exactly-${number}-hens` = 'exactly-2.89204-hens'
Template Literals (cont.)
Template literals can also be combined inside template literals.
type SkiPrefix = `ski${string}`type FulSuffix = `${string}ful`
let valid: `${SkiPrefix}${FulSuffix}` = 'skillful'let invalid: `${SkiPrefix}${FulSuffix}` = 'skiful'// skiful not assignable// to `ski${string}${string}ful`
Template Literals (other cool stuff)
There's all kinds of neat things you can do with template literals.
A type's properties can be accessed - like a normal TS object - to get a specific property's underlying type.
type GeneralUKLocation = {
country: 'Wales' | 'Scotland' | 'Northern Ireland' | 'England'distanceFromLondon: numberdateFounded: Date
}
let noOneKnowsReally: GeneralUKLocation['country'] = 'Wales'let elseWhere: GeneralUKLocation['country'] = 'France'// does not compile// string is not assignable to type number | Datelet someKindOfUnion: GeneralUKLocation['distanceFromLondon' | 'dateFounded'] = 'Scotland'
keyof
Similar to Object.keys(obj) we can get a list of properties in a type.
type Color = {
name: stringred: number,
blue: number,
green: numberalpha: number
}
const greenAttr: keyof Color = 'green'const orangeAttr: keyof Color = 'orange'// 'orange' not assignable to// 'name' | 'red' | 'blue' | 'green' | 'alpha'
Generics
We can use generics to write code that doesn't care about specific types.
combineWithGreeter('uh', 'what'); // compiles, but error at runtime// Uncaught TypeError: Cannot assign to read only property '0' of object '[object String]'
We can use extends to limit the allowed types to a subset of types.
type DisallowTriplets<T extends unknown[]> = T['length'] extends3 ? never : T;
const arbitraryNumbers: DisallowTriplets<number[]> = [1,2]
const threeOfThem: DisallowTriplets<[number, number, number]> = [1, 2, 3] // does not compile// [1,2,3] not assignable to never
Conditional types (infer)
We can pull out parts of types that satisfy type conditions.
type DropsLLC<T extendsstring> = T extends`${infer company}, LLC` ? company : T
const pepsi: DropsLLC<'PepsiCo, LLC'> = 'PepsiCo'// compilesconst cocaColaLLC: DropsLLC<'Coca-Cola INC'> = 'Coca-Cola'; // does not compileconst cocaCola: DropsLLC<'Coca-Cola INC'> = 'Coca-Cola INC'; // compiles
We can do this with lists as well.
type FirstItemExtracted<T extends unknown[]> =
T extends [infer first, ...infer rest] ?
{first, rest} :
{first: null, rest: []}
// T extends [infer first, ...infer rest]const numbers : FirstItemExtracted<[1,2,3]> = {first: 1, rest: [2,3]} // matchconst oneNumber : FirstItemExtracted<[1]> = {first: 1, rest: []} // matchconst empty : FirstItemExtracted<[]> = {first: null, rest: []} // did not match
Recursion
We can make types that refer to themselves using recursion.
type Tree<T> = T | { left: Tree<T>, right: Tree<T> }