Sometimes, it’s handy to have a simple reference to peek at and continue coding. If you want a refresher on types compatibility, syntax, interfaces, or other TypeScript nuances, please bookmark this cheat sheet! We’ll continue to update it with new hints over time.
Without further ado, let’s dive into it!
Types of types 🙂
Dynamic – types evaluated during runtime (JS, Python, Ruby)
let a = 1;
a = '1';
Static – type checking performed during compilation (types must be known, C, Java)
let a: number = 1;
a = '1'; // Error
Weak – types can be changed for a certain purpose (f.e. comparison): C, JS
const a = 4 - '3' // 1
const b = 4 + '3' // 43
'555' == 555 // true
const c = undefined;
c == null // true
Strong – types never change and are strictly compared – exception in TS if
statements:
const a = 'a';
if (a) {} // exception
Structural – types must be structurally compatible
type A = { name: string };
type B = { name: string };
const a: A = { name: 'a' };
const b: B = a;
Nominal – compatibility by declaration (opposite to structural). Example: Java classes.
Types compatibility
Structural compatibility takes place for classes with only public fields. Static types aren’t compared; hence having different constructors and static fields doesn’t break compatibility
class A {
id: number;
}
type B = {
id: number;
}
const a = new A();
const b: B = a; // OK
const c: A = { id: 1 }; // OK
class A {
static a = 1;
constructor(id: number) {}
}
class B {
static b = 'b'
constructor(name: string) {}
}
const a: A = new B(1); // OK
const b: B = new A('a'); // OK
Object literal assignment is correct only for compatible types
type A = { a: number };
type B = { a: number; b: number };
const b: B = { a: 1, b: 2 };
const a1: A = b; // OK
const a2: A = { a: 1, b: 2 }; // Error
Subtype compatibility
type A = { a: number };
type B = { a: number; b: number };
let a: A = { a: 1 };
let b: B = { a: 1, b: 2 };
a = b;
b = a; // Error
Function compatibility
type A = { a: number };
type B = { a: number; b: number };
const a: A = { a: 1 };
const b: B = { a: 1; b: 2 };
function doA(a: A) {}
function doB(b: B) {}
doA(a); // OK
doA(b); // OK
doB(a); // Error
doB(b); // OK
let doAA = function(a: A) {}
let doBB = function(b: B) {}
doAA = doBB; // Error (doesn't apply to object methods - it passes there)
doBB = doAA; // OK
let doAAA = (): A => ({ a: 1 });
let doBBB = (): B => ({ a: 1, b: 2 });
doAAA = doBBB; // OK
doBBB = doAAA; // Error
type Opt = (a: number, b?: number) => void;
type Req = (a: number, b: number) => void;
let opt: Opt = (a, b) => {};
let req: Req = (a, b) => {};
opt = req; // Error
req = opt; // OK
Covariance – using subtype (extension of X
) instead of a type X
: Function return type is covariant
Contravariance – using supertype (more general than X
) instead of type X
Function argument type is contravariant
Bivariance – using subtype or supertype instead of type Object
method argument type is bivariant
type A = { a: number };
type B = { a: number; b: number };
type C = { a: number; b: number; c: number };
type Fn = (x: B) => B;
type subtypeFn = (x: A) => C; // subtype of Fn
BivarianceHack – enables using bivariant types in funtions – object methods are bivariant so defining a type with a method and accessing it produces bivariant type:
type Bivariant<T> = {
bivarianceHack(x: T): void;
}['bivarianceHack'];
Language
Merging interfaces with the same name making them easily extendable (usable for extending external libraries)
interface A {
a: number;
}
interface A {
b: number;
}
const a: A = {
a: 1,
b: 1,
}
Private fields in JS (#) and TS (private
)
class A {
private a: number; // removed during compilation (becomes public)
}
class B {
#b: number; // remains private after compilation
}
Unique symbol – assigning a symbol to a const produces unique symbol
type making it impossible to be compared and assigned (acting as nominal types)
const a: unique symbol = Symbol.for('a'); // same as typeof a
unions and intersections A | B
– only common fields from both sets
A & B
– all fields from both sets
as const
– used for strings makes them literals, used for objects assigns a read-only attribute to its fields
const a = 'a' as const; // type 'a'
const obj = {
a: 'a',
} as const;
typeof obj; // { readonly a: 'a' }
**asserts x is y'** - used as type guard similar to
x is y’ but an assertion type for functions that throw an exception
function assertsIsA(a: any): asserts is A {
if (!isA(a)) throw new Error();
}
typeof
for types – returns type instead of a string with the type
const obj = { a: 1 };
type A = typeof obj; // { a: number }
const a = typeof obj; // "object"
typeof arr[number]
– returns type of array content
const arr= [1, 'a'];
typeof arr[number] // string | number
obj[keyof obj]
– returns type of each object fields
type obj = {
a: number;
b: string;
};
type a = obj[keyof obj]; // string | number
keyof any
– returns types that can be used for object indexing
type Keys = K extends keyof any; // string | number | symbol
variadic tuples
type A = [...T, ...K];
Distributive conditional types – using conditional type on union is like using condition on each union component
type A = Condition<number | string | null>;
type B = Condition<number> | Condition<string> | Condition<null>;
Deferred conditional types – type remains conditional until its condition can be verified
declare function getY<T>(x: T): T extends Entity ? string : number;
function getData<U>(x: U) {
return getY(x); // U extends Entity ? string : number
}
Conditional type – is compatible with a union of its results
declare function getY<T>(x: T): T extends Entity ? string : number;
function getData<U>(x: U) {
const y = getY(x); // U extends Entity ? string : number
const z: string | number = y; // OK
}
infer
– infer the type and assign it to variable
function randomData(x: string) {
return 5;
}
type ReturnType<T> = T extends (x: string) => infer R ? R : never;
type T = ReturnType<typeof randomData>; // number
Branding – enables using nominal typing (typescript has structural typing)
type A = {
__brand: 'A';
id: number;
};
type B = {
__brand: 'B';
id: number;
};
function getId(value: A) {
return value.id;
}
declare const a: A;
declare const b: B;
getId(a); // OK
getId(b); // Error
type C = number & { __brand: 'C' };
type D = number & { __brand: 'D' };
declare let c: C;
declare let d: D;
c = d; // Error
Flavoring – enables using nominal types with optional literal so values can be assigned
type A = {
__brand?: 'A';
id: number;
};
Kudos to https://typescriptnapowaznie.pl/ and https://typescript-book.com/ for being an inspiration to write this post!