Useful links :
noImplicitAny
strictNullChecks
Check truthiness via double-Boolean negation :
Boolean("hello"); // type: boolean, value: true
!!"world"; // type: true, value: true
Assert that a value is for sure not null or undefined :
function liveDangerously(x?: number | null) {
console.log(x!.toFixed());
}
unknown is similar to any but cannot be used :
function f1(a: any) {
a.b(); // OK
}
function f2(a: unknown) {
a.b(); // Error: 'a' cannot be used
}
function safeParse(s: string): unknown {
return JSON.parse(s);
}
const obj = safeParse(someRandomString); // Need to be careful with 'obj'!
never, for future-proofing :
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
// 'never' ensure that expanding the Shape type will generate an error if
// we later add another Shape in the union (ex: type Shape = Circle | Square | Triangle)
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
keyof builds a union type of all keys from an existing type :
type User = {
id: number;
name: string;
isAdmin: boolean;
};
type UserKeys = keyof User; // UserKeys: "id" | "name" | "isAdmin";
Read-only arrays :
const readonlyArray: ReadonlyArray<string> = ["red", "green", "blue"];
function doStuff(values: readonly string[]) {
console.log(`The first value is ${values[0]}`); // read
values.push("hello!"); // error: Property 'push' does not exist on type 'readonly string[]'.
}
Optional parameters :
function printName(obj: { first: string; last?: string }) {}
function f(x?: number) {}
function f(x: number = 10) {}
Parameter destructuring :
function sum({ a, b, c }: { a: number; b: number; c: number }) {
console.log(a + b + c);
}
// or
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
console.log(a + b + c);
}
“Options” parameter :
// Only 'last' is optional
function sayName({ first, last = 'Smith' }: { first: string; last?: string }) {}
// Everything is optional
function sayName({ first='Bob', last='Smith'}: { first?: string; last?: string} = {}) {}
“Options” parameter with default values :
function paintShape({ shape, xPos = 0, yPos = 0 }: PaintOptions) {}
Variable amount of parameters :
function multiply(n: number, ...m: number[]) {
return m.map((x) => n * x);
}
const a = multiply(5, 10, 20, 30); // a = [50,100,150]
Function overloads :
function makeDate(timestamp: number): Date; // Overload signature 1 (can be called)
function makeDate(m: number, d: number, y: number): Date; // Overload signature 2 (can be called)
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date { // Implementation signature (cannot be called)
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}
}
const d1 = makeDate(12345678); // Calls signature 1
const d2 = makeDate(5, 5, 5); // Calls signature 2
const d3 = makeDate(1, 3); // Error
Type :
type Point = {
x: number;
y: number;
};
type Point3D = Point & {
z: number;
}
Interface :
interface Point {
x: number;
y: number;
}
interface Point3D extends Point {
z: number;
}
interface Point { // Modifies the existing interface
name: string
}
Optional property :
interface PaintOptions {
shape: Shape;
xPos?: number;
yPos?: number;
}
Union types :
type ColorfulCircle = Colorful | Circle;
Intersection types :
type ColorfulCircle = Colorful & Circle;
Using types to define functions through fn :
type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {}
Tuple :
type StringNumberPair = [string, number];
function doSomething(stringHash: StringNumberPair) {
const [inputString, hash] = stringHash; // Destructuring
}
// Tuples can have a variable amount of elements
type StringNumberBooleans = [string, number, ...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];
// Read-only variant
function doSomething(pair: readonly [string, number]) {}
in narrowing :
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) { // animal is narrowed to Fish
return animal.swim();
}
return animal.fly();
}
instanceof narrowing :
function logValue(x: Date | string) {
if (x instanceof Date) {
console.log(x.toUTCString()); // x: Date
} else {
console.log(x.toUpperCase()); // x: string
}
}
Declared Type vs Observed Type :
let x = Math.random() < 0.5 ? 10 : "hello world!"; // x: string | number
x = 1; // x: number
x = "goodbye!"; // x: string
is type predicate :
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
let pet = getSmallPet();
if (isFish(pet)) { // Type is automatically narrowed to Fish
pet.swim();
} else {
pet.fly();
}
// We can use the type predicate for filtering
const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];
const underWater1: Fish[] = zoo.filter(isFish);
Enforce type narrowing :
declare function handleRequest(url: string, method: "GET" | "POST"): void;
const req = { url: "https://example.com", method: "GET" };
// No guarantee that the value of `method` has not changed between its declaration and the call to `handleRequest`
handleRequest(req.url, req.method);
Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
3 solutions :
// Assert the type in the declaration
const req = { url: "https://example.com", method: "GET" as "GET" };
// Assert the type in the call
handleRequest(req.url, req.method as "GET");
// Convert the entire object into type literals :
const req = { url: "https://example.com", method: "GET" } as const;
Narrowing through common properties :
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
if (shape.kind === "circle") { // Type narrowed to Circle
return Math.PI * shape.radius ** 2;
}
}
Generic function type :
function firstElement<Type>(arr: Type[]): Type | undefined {
return arr[0];
}
const s = firstElement(["a", "b", "c"]); // s: string
const n = firstElement([1, 2, 3]); // n: number
const u = firstElement([]); // u: undefined
Generic type definition :
interface Box<Type> {
contents: Type;
}
// Or
type Box<Type> = {
contents: Type;
};
let box: Box<string>;
Constrain a generic function :
// "Type" includes the 'length' property. No length => invalid type
function longest<Type extends { length: number }>(a: Type, b: Type) {
if (a.length >= b.length) {
return a;
} else {
return b;
}
}
const longerArray = longest([1, 2], [1, 2, 3]); // longerArray: number[]
const longerString = longest("alice", "bob"); // longerString: "alice" | "bob"
const notOK = longest(10, 100); // Error! Numbers don't have a 'length' property
Enforce the generic type, but from the caller :
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2);
}
// Error, the generic function can only return one type, and it picks the first one it found (number[])
const arr = combine([1, 2, 3], ["hello"]);
// Will work, we explicitely tell TS what type the generic function will return
const arr = combine<string | number>([1, 2, 3], ["hello"]);
Awaited<Type> : returned by a Promise chain
type A = Awaited<Promise<string>>; // A: string
type B = Awaited<Promise<Promise<number>>>; // B: number
type C = Awaited<boolean | Promise<number>>; // C: number | boolean
Partial<Type> : “copy” of a Type where everything is optional
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
Required<Type> : “copy” of a Type where everything is required (= opposite of Partial)
interface Props {
a?: number;
b?: string;
}
const obj: Props = { a: 5 };
const obj2: Required<Props> = { a: 5 }; // Error: Property 'b' is missing
Readonly<Type> : “copy” of a Type where everything is read-only
interface Todo {
title: string;
}
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
todo.title = "Hello"; // Error: Cannot assign to 'title' because it is a read-only property.
Record<Keys, Type> : Used to map a list of keys onto another Type
type CatName = "miffy" | "boris" | "mordred";
interface CatInfo {
age: number;
}
const cats: Record<CatName, CatInfo> = {
miffy: { age: 10 },
boris: { age: 5 },
mordred: { age: 16 },
};
Pick<Type, Keys> : Whitelist some keys from an existing Type
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
Omit<Type, Keys> : Blacklist some keys from an existing Type. Opposite of Pick
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Omit<Todo, "description" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
};
Exclude<UnionType, ExcludedMembers> : Blacklist some members of a type union
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
type T3 = Exclude<Shape, { kind: "circle" }>;
Extract<Type, Union> : Get all members in common from 2 unions
type T0 = Extract<"a" | "b" | "c", "a" | "b", "f">; // "a" | "b"
NonNullable<Type> : Get a type without null and undefined values
type T0 = NonNullable<string | number | undefined>; // string | number
Parameters<Type> : contains all parameters of a function
type T0 = Parameters<() => string>; // []
type T1 = Parameters<(s: string) => void>; // [s: string]
ConstructorParameters<Type> : contains all parameters of a constructor
class C {
constructor(a: number, b: string) {}
}
type T3 = ConstructorParameters
ReturnType<Type> : contains
declare function f1(): { a: number; b: string };
type T4 = ReturnType<typeof f1>; // type T4 = { a: number; b: string; }
Define an indexable type :
interface StringArray {
[index: number]: string;
}
const myArray: StringArray = ["abc", "def"];
const secondItem = myArray[1]; // secondItem: string
Define an indexable & read-only type :
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = getReadOnlyStringArray();
myArray[2] = "Mallory"; // Error, can only be read
Error when passing an object with too many properties :
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig) {}
let mySquare = createSquare({ other: "red", width: 100 }); // Error, 'other' is not needed
// Solution 1 :
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
// Solution 2
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: unknown;
}
Enums (Documentation) :
Examples
// Base case
enum Direction {
Up,
Down,
Left,
Right,
}
// With a value
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
// Auto-incremented
enum Direction {
Up = 1,
Down,
Left,
Right,
}