undefined
:undefined
boolean
:true
false
number
:0
0b0
0o0
0x0
0.0
bigint
:100n
string
:''
""
`${expr}`
symbol
:Symbol()
Symbol.for('key')
null
:null
object
: notundefined
,boolean
,number
,bigint
,string
,symbol
ornull
NOTE:
typeof null === 'object'
andtypeof parseInt === 'function'
.
array
:ReadonlyArray<T>
readonly Array<T>
readonly T[]
Array<T>
T[]
tuple
:readonly [T1, T2, ...]
[T1, T2, ...]
any
:T | any
unknown
void
:undefined
void T
never
:throw Error()
while (true) {}
...
value as const
value as Type
<const>value
<Type>value
value!
let isDone: boolean;
isDone = true;
let isDone: boolean = true;
let isDone = true;
const isDone: boolean = true;
const isDone = true;
const person: { name: string; age: number } = { name: 'John', age: 21 };
const person = { name: 'John', age: 21 };
const person: { readonly name: string; readonly age: number } = {
name: 'John',
age: 21,
};
// { readonly name: 'John'; readonly age: 21; }
const person = { name: 'John', age: 21 } as const;
function add(a: number, b: number): number {
return a + b;
}
const add: (a: number, b: number) => number = (a, b) => a + b;
function add(a: number, b: number) {
return a + b;
}
const add = (a: number, b: number) => a + b;
function add(a: number, b: number | undefined = undefined) {
if (typeof b === 'undefined') {
return a + 1;
}
return a + b;
}
function add(a: number, b: void | number) {
if (typeof b === 'undefined') {
return a + 1;
}
return a + b;
}
function add(a: number, b?: number) {
if (typeof b === 'undefined') {
return a + 1;
}
return a + b;
}
function add(a: number, b: number = 1) {
return a + b;
}
function sum(a: number, ...nums: number[]) {
return nums.reduce((a, b) => a + b, a);
}
// This is JavaScript code.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
class Person {
// One of "[public | protected | private] [readonly]"
constructor(public name: string, public age: number) {}
}
class Component {
// "!" indicates this field is set by external code
context!: string;
printContext() {
console.log(this.context);
}
}
interface LabeledValue {
label: string;
}
function printLabel(labeledObj: LabeledValue) {
console.log(labeledObj.label);
}
const myObj = { size: 10, label: 'Size 10 Object' };
printLabel(myObj);
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
const newSquare = { color: 'white', area: 100 };
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
const mySquare = createSquare({ color: 'black' });
Note: Both
{}
and{ prop: undefined }
are assignable to{ prop?: T }
, but{}
not to{ prop: T | undefined }
.
Object literals get special treatment and undergo excess property checking when assigning them to other variables, or passing them as arguments.
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
// ...
}
// Error: There’s probably a bug in this code.
const mySquare = createSquare({ colour: 'red', width: 100 });
interface Indexable<T, V extends T> {
[key: string]: T;
[key: number]: V;
}
Note: There are two types of supported index signatures:
string
andnumber
. Because JavaScript runtime treatsindexable[100]
asindexable['100']
,Indexable[number]
must be a subtype ofIndexable[string]
.
Some properties should only be modifiable when an object is first created.
interface Point {
readonly x: number;
readonly y: number;
}
Note:
readonly
prevents from modifying whileconst
prevents from re-assigning.
interface SearchFunc {
(source: string, subString: string): boolean;
}
const mySearch: SearchFunc = (src, sub) => {
const result = src.search(sub);
return result > -1;
};
interface ClockInterface {
// Can't be `private` or `protected`.
// See "Interfaces Extending Classes"
currentTime: Date;
setTime(d: Date): void;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
}
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick(): void;
}
function createClock(
ctor: ClockConstructor,
hour: number,
minute: number
): ClockInterface {
return new ctor(hour, minute);
}
class DigitalClock implements ClockInterface {
constructor(h: number, m: number) {}
tick() {
console.log('beep beep');
}
}
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) {}
tick() {
console.log('tick tock');
}
}
const digital = createClock(DigitalClock, 7, 32);
const analog = createClock(AnalogClock, 12, 17);
interface Shape<T> {
shadowed: T;
}
interface Square<T, V extends T> extends Shape<T> {
shadowed: V;
}
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
interface Counter {
(start: number): void;
interval: number;
reset(): void;
}
function getCounter(): Counter {
const counter = (function () {} as unknown) as Counter;
counter.interval = 123;
counter.reset = function () {};
return counter;
}
When an interface type extends a class type it inherits the members of the class. If an interface extends a class with private or protected members, that interface type can only be implemented by that class or a subclass of it.
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() {}
}
class TextBox extends Control {
select() {}
}
// Error: Types have separate declarations of a private property 'state'.
class Image implements SelectableControl {
private state: any;
select() {}
}
class Overload {
prop: string;
constructor();
constructor(prop: string);
constructor(prop?: string) {
// SyntaxError: `this(prop);`
this.prop = prop ?? 'Unknown';
}
method(a: number): string;
method(a: string): number;
method(a: number | string): string | number {
if (typeof a === 'number') {
return `${this.prop}: ${a}`;
}
return parseInt(a);
}
}
class Animal<T, V> {
constructor(public name: string) {}
method(_arg: T): V {
return null as any;
}
}
class Monkey<T, V, T1 extends T, V1 extends V> extends Animal<T1, V> {
constructor(name: string) {
super(name);
}
method(_arg: T): V1 {
super.method(_arg as T1);
return null as any;
}
}
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {}
}
class Employee {
private _fullName!: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
this._fullName = newName;
}
}
Note: Accessors with a
get
and noset
are automatically inferred to bereadonly
.
class Grid {
static origin = { x: 0, y: 0 };
constructor(public scale: number) {}
calculateDistanceFromOrigin(point: { x: number; y: number }) {
const xDist = point.x - Grid.origin.x;
const yDist = point.y - Grid.origin.y;
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
}
For two types to be considered compatible, if one of them has a private
member, then the other must have a private
member that originated in the same declaration. The same applies to protected
members.
// `public` by default and freely access
class Animal {
public name: string;
public constructor(theName: string) {
this.name = theName;
}
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
// `private` restricts in containing class
class Animal {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
// `protected` reaches to deriving classes
class Person {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
// Note: `super.name` is `undefined`.
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
abstract class Department {
constructor(public name: string) {}
// must be implemented in derived classes
// can't mark as `private`
abstract getFullname(): string;
printName(): void {
console.log('Department name: ' + this.getFullname());
}
}
class Greeter {
static standardGreeting = 'Hello, there';
greeting!: string;
greet() {
if (this.greeting) {
return 'Hello, ' + this.greeting;
} else {
return Greeter.standardGreeting;
}
}
}
class MyGeeter extends Greeter {}
function greet(greeterMaker: typeof Greeter) {
greeterMaker.standardGreeting = 'Hey there!';
const greeter: Greeter = new greeterMaker();
console.log(greeter.greet());
}
greet(MyGeeter);
greet(Greeter);
this
parameters are fake parameters that come first in the parameter list of a function:
function f(this: void) {
// make sure `this` is unusable in this standalone function
}
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
const deck: Deck = {
suits: ['hearts', 'spades', 'clubs', 'diamonds'],
cards: Array(52),
createCardPicker(this: Deck) {
return () => {
const pickedCard = Math.floor(Math.random() * 52);
const pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
},
};
const cardPicker = deck.createCardPicker();
const { suit, card } = cardPicker();
declare const addClickListener: (
onclick: (this: void, e: Event) => void
) => void;
class Handler {
type!: string;
onClickGood = (e: Event) => {
this.type = e.type;
};
}
addClickListener(new Handler().onClickGood);
Note: An arrow function cannot have a
this
parameter. Read Understanding JavaScript Function Invocation and “this”.
function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x: any): any {
const suits = ['hearts', 'spades', 'clubs', 'diamonds'];
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == 'object') {
const pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == 'number') {
const pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
Note:
- Order overloads from most specific to least specific.
- The last piece is not part of the overload list.
- Each enum member can be numeric or string value.
- Only numeric members can be reverse mapping.
- Each enum member can be constant or computed.
A constant is an expression that can be fully evaluated at compile time.
- When each member is constant:
- Enum members also become types as well.
- The enum becomes a union type of all members.
enum FileAccess {
// constant members
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// computed member
G = '123'.length,
}
enum Enum {
A,
}
Enum[Enum.A]; // "A"
enum ShapeKind {
Circle,
Square,
}
interface Circle {
kind: ShapeKind.Circle;
radius: number;
}
interface Square {
kind: ShapeKind.Square;
sideLength: number;
}
const
enums members are expanded to values.const
enums cannot have computed members.const
enums cannot be used as values.
const enum Reply {
YES,
NO,
}
// expanded: [0 /* YES */, 1 /* NO */];
const replies = [Reply.YES, Reply.NO];
enum KeyOfShape {
COLOR = 'color',
WIDTH = 'width',
}
interface Shape {
[KeyOfShape.COLOR]: string;
[KeyOfShape.WIDTH]: number;
}
type KeyOfShapeNames = keyof typeof KeyOfShape;
function valueOf<T, K extends keyof T>(t: T, k: K): T[K] {
return t[k];
}
function keyOf<T>(t: T, v: T[keyof T]): keyof T {
for (const k in t) {
if (t[k] === v) {
return k;
}
}
throw Error();
}
Generics is about relationships of types. The power of a system comes more from the relationships among types than from the types themselves.
The identity function is a function that will return back whatever is passed in.
// limited to accept a specific type
function identity(arg: number): number {
return arg;
}
// losing type information when returns
function identity(arg: any): any {
return arg;
}
// the generic way, use a type variable for
// capturing type information when provided.
function identity<T>(arg: T): T {
return arg;
}
// explicitly pass in the type arguments
identify<string>('myString');
// rely on type argument inference
identify('myString');
function identity<T>(arg: T): T {
return arg;
}
const identity: <T>(arg: T) => T = arg => arg;
const identityObj = {
identity<T>(arg: T): T {
return arg;
},
};
class IdentityCls {
identity<T>(arg: T): T {
return arg;
}
}
interface GenericIdentityFn {
<T>(arg: T): T;
}
interface GenericIdentityFn<T> {
(arg: T): T;
}
interface Addable<T> {
readonly zeroValue: T;
value: T;
add(other: Addable<T>): Addable<T>;
}
class MyNumber implements Addable<number> {
readonly zeroValue = 0;
constructor(public value: number) {}
add(other: MyNumber): MyNumber {
return new MyNumber(this.value + other.value);
}
}
class Container<T> {
constructor(private item: T) {}
store(item: T) {
this.item = item;
}
fetch(): T {
return this.item;
}
}
Note: Static members can not use the class’s type parameter.
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
function create<T>(c: { new (): T }): T {
return new c();
}
- There are two sets of literal types available in TypeScript today, strings and numbers.
- Using
const
to declare a variable triggers literal narrowing, whilevar
orlet
don't. - Literal types combine nicely with union types, type guards and type aliases.
// type "Hello World"
const helloWorld = 'Hello World';
// type string
let hiWorld = 'Hi World';
type Easing = 'ease-in' | 'ease-out' | 'ease-in-out';
class UIElement {
animate(dx: number, dy: number, easing: Easing) {
if (easing === 'ease-in') {
// ...
} else if (easing === 'ease-out') {
// ...
} else if (easing === 'ease-in-out') {
// ...
} else {
// Error: No more values here.
}
}
}
const button = new UIElement();
button.animate(0, 0, 'ease-in');
// Error: "uneasy" is not allowed here.
button.animate(0, 0, 'uneasy');
function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 {
// Error: Type '0' is not assignable to type '1 | 2 | 3 | 4 | 5 | 6'
return 0;
}
function foo(x: number) {
if (x !== 1 || x !== 2) {
// ~~~~~~~
// Error: The types '1' and '2' have no overlap.
}
}
An intersection type combines multiple types into one, for example, Person & Serializable & Loggable
. That means an object of this type will have all members of all three types.
function extend<F, S>(first: F, second: S): F & S {
const result: Partial<F & S> = {};
for (const prop in first) {
if (Object.prototype.hasOwnProperty.call(first, prop)) {
(result as F)[prop] = first[prop];
}
}
for (const prop in second) {
if (Object.prototype.hasOwnProperty.call(first, prop)) {
(result as S)[prop] = second[prop];
}
}
return result as F & S;
}
function padLeft(value: string, padding: number | string) {
if (typeof padding === 'number') {
return Array(padding + 1).join(' ') + value;
}
if (typeof padding === 'string') {
return padding + value;
}
}
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
// Type predicate: A predicate takes the form `parameterName is Type`.
function isFish(pet: Fish | Bird): pet is Fish {
return 'swim' in pet;
}
function playSmallPet(pet: Fish | Bird) {
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
}
function move(pet: Fish | Bird) {
if ('swim' in pet) {
return pet.swim();
}
return pet.fly();
}
The typeof
type guards are recognized in two different forms:
typeof v === <typename>
typeof v !== <typename>
where <typename>
must be:
'boolean'
'string'
'number'
'symbol'
Note: There are three more possible values of
typeof v
:
'undefined'
'function'
'object'
interface Padder {}
class SpaceRepeatingPadder implements Padder {}
class StringPadder implements Padder {}
function applyRandomPadder(padder: SpaceRepeatingPadder | StringPadder) {
if (padder instanceof SpaceRepeatingPadder) {
padder; // type narrowed to 'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
padder; // type narrowed to 'StringPadder'
}
}
function assert(condition: any, msg?: string): asserts condition {
if (!condition) {
throw new Error(msg);
}
}
function yell(str: any) {
assert(typeof str === 'string');
return str.toUppercase();
// ~~~~~~~~~~~
// Error: Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?
}
function assertIsString(val: any): asserts val is string {
if (typeof val !== 'string') {
throw new Error('Not a string!');
}
}
By default, null
and undefined
assignable to anything, but this is a “billion dollar mistake”. Fortunately, the --strictNullChecks
flag fixes this:
let s = 'foo';
s = null; // Error: 'null' is not assignable to 'string'
let s: string | null = 'bar';
s = null; // OK
s = undefined; // Error: 'undefined' is not assignable to 'string | null'
Optional Parameters and Properties automatically add | void
:
function f(x: number, y?: number) {
// ...
}
f(1);
f(1, 2);
f(1, undefined);
f(1, null); // Error: 'null' is not assignable to 'number | void'
class C {
constructor(public a: number, public b?: number) {}
}
new C(1);
new C(1, 2);
new C(1, undefined);
new C(1, null); // Error: 'null' is not assignable to 'number | void'
Use optional chaining to simplify working with nullable types:
const x = foo === null || foo === undefined ? undefined : foo.bar.baz();
// optional property access
const x = foo?.bar.baz();
function f(foo?: number[] | null) {
// optional element access
foo?.[0];
}
function f(foo?: (() => void) | null) {
// optional call
foo?.();
}
const x = foo !== null && foo !== undefined ? foo : bar();
const x = foo ?? bar();
Note: Comparing with
val || alt
,val ?? alt
avoids some unintended behavior from0
,NaN
,false
and''
being treated as falsy values.
function f(arg?: number | null): number {
return arg!;
}
Type aliases create a new name for a type, doesn’t actually create a new type.
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
};
// This is how type aliases can be extended.
type LinkedList<T> = T & { next: LinkedList<T> };
interface Person {
name: string;
}
Note: For being open to extension, use an interface over a type alias if possible.
There are three ingredients:
- Types that have a common, singleton type property — the discriminant.
- String literal types.
- Numeric literal types.
- Enum member types.
- A type alias that takes the union of those types — the union.
- Type guards on the common property.
// Step 1
interface Square {
kind: 'square';
size: number;
}
interface Rectangle {
kind: 'rectangle';
width: number;
height: number;
}
interface Circle {
kind: 'circle';
radius: number;
}
// Step 2
type Shape = Square | Rectangle | Circle;
// Step 3
function area(s: Shape) {
switch (s.kind) {
case 'square':
return s.size * s.size;
case 'rectangle':
return s.height * s.width;
case 'circle':
return Math.PI * s.radius ** 2;
}
}
function area(s: Shape) {
switch (s.kind) {
case 'square':
return s.size * s.size;
case 'rectangle':
return s.height * s.width;
}
// should error here - case 'circle' not handled
}
There are three ways to tell the compiler to cover all variants of the discriminated union.
- Turn on
--noImplicitReturns
to Ensure that all codepaths return in a function. - Turn on
--strictNullChecks
and specify a return type. - Use the
never
type to check for exhaustiveness.
function assertNever(x: never): never {
throw new Error('Unexpected object: ' + x);
}
function area(s: Shape) {
switch (s.kind) {
case 'square':
return s.size * s.size;
case 'rectangle':
return s.height * s.width;
default:
// error here if there are missing cases
return assertNever(s);
}
}
class BasicCalculator {
constructor(protected value: number) {}
currentValue(): number {
return this.value;
}
// Change `this` to `BasicCalculator` to see what happens.
multiply(operand: number): this {
this.value *= operand;
return this;
}
}
class ScientificCalculator extends BasicCalculator {
constructor(value = 0) {
super(value);
}
sin() {
this.value = Math.sin(this.value);
return this;
}
// ... other operations go here ...
}
const v = new ScientificCalculator(2).multiply(5).sin().currentValue();
keyof T
, the index type query operator.T[keyof T]
, the indexed access operator.
function getProperty<T, K extends keyof T>(o: T, propertyName: K): T[K] {
// o[propertyName] is of type T[K]
return o[propertyName];
}
const taxi = {
manufacturer: 'Toyota',
model: 'Camry',
year: 2014,
};
const name: string = getProperty(taxi, 'manufacturer');
const year: number = getProperty(taxi, 'year');
interface Dictionary<T> {
[key: string]: T;
}
let keys: keyof Dictionary<number>; // string | number
let val1: Dictionary<number>[string]; // number;
let val2: Dictionary<number>[number]; // number;
let val3: Dictionary<number>['foo']; // number
let val4: Dictionary<number>[123]; // number
interface Dictionary<T> {
[key: number]: T;
}
let keys: keyof Dictionary<number>; // number
let val1: Dictionary<number>[number]; // number;
let val2: Dictionary<number>[42]; // number
The syntax resembles the syntax for index signatures with a for .. in
inside.
type Keys = 'option1' | 'option2';
type Flags = { [K in Keys]: boolean };
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type Record<K extends keyof any, T> = {
[P in K]: T;
};
// use an intersection type to add new members
type PartialWithNewMember<T> = {
[P in keyof T]?: T[P];
} & { newMember: boolean };
type Proxy<T> = {
get(): T;
set(value: T): void;
};
type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>;
};
function proxify<T>(o: T): Proxify<T> {
const result = {} as Proxify<T>;
for (const k in o) {
result[k] = {
get() {
return o[k];
},
set(value) {
o[k] = value;
},
};
}
return result;
}
function unproxify<T>(t: Proxify<T>): T {
let result = {} as T;
for (const k in t) {
result[k] = t[k].get();
}
return result;
}
A conditional type T extends U ? X : Y
, when T
is assignable to U
the type is X
, otherwise the type is Y
.
type TypeName<T> = T extends string
? 'string'
: T extends number
? 'number'
: T extends boolean
? 'boolean'
: T extends undefined
? 'undefined'
: T extends Function
? 'function'
: 'object';
type T0 = TypeName<string>; // 'string'
type T1 = TypeName<'a'>; // 'string'
type T2 = TypeName<true>; // 'boolean'
type T3 = TypeName<() => void>; // 'function'
type T4 = TypeName<string[]>; // 'object'
T extends U ? X : Y
is either resolved to X
or Y
, or deferred - where they stick around instead of picking a branch.
declare function f<T extends boolean>(x: T): T extends true ? string : number;
// Type is 'string | number'
const x = f(Math.random() < 0.5);
interface Foo {
propA: boolean;
propB: boolean;
}
declare function f<T>(x: T): T extends Foo ? string : number;
function foo<U>(x: U) {
// `U extends Foo ? string : number` is assignable to `string | number`
const b: string | number = a;
}
T extends U ? X : Y
in which T
is a naked type parameter are called distributive conditional types. Distributive conditional types are automatically distributed over union types during instantiation. For example, an instantiation of T extends U ? X : Y
with the type argument A | B | C
for T
is resolved as (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
.
type BoxedValue<T> = { value: T };
type BoxedArray<T> = { array: T[] };
type Boxed<T> = T extends any[] ? BoxedArray<T[number]> : BoxedValue<T>;
type T20 = Boxed<string>; // BoxedValue<string>;
type T21 = Boxed<number[]>; // BoxedArray<number>;
type T22 = Boxed<string | number[]>; // BoxedValue<string> | BoxedArray<number>;
type Diff<T, U> = T extends U ? never : T; // Remove types from T that are assignable to U
type Filter<T, U> = T extends U ? T : never; // Remove types from T that are not assignable to U
type T30 = Diff<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'>; // 'b' | 'd'
type T31 = Filter<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'>; // 'a' | 'c'
type T32 = Diff<string | number | (() => void), Function>; // string | number
type T33 = Filter<string | number | (() => void), Function>; // () => void
type NonNullable<T> = Diff<T, null | undefined>; // Remove null and undefined from T
type T34 = NonNullable<string | number | undefined>; // string | number
type T35 = NonNullable<string | string[] | null | undefined>; // string | string[]
Note:
T | never
=T
,T & never
=never
.
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
Within the extends
clause of a conditional type, it is now possible to have infer
declarations that introduce a type variable to be inferred. Such inferred type variables may be referenced in the true branch of the conditional type. It is possible to have multiple infer
locations for the same type variable.
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
type Unpacked<T> = T extends (infer U)[]
? U
: T extends (...args: any[]) => infer U
? U
: T extends Promise<infer U>
? U
: T;
type T0 = Unpacked<string>; // string
type T1 = Unpacked<string[]>; // string
type T2 = Unpacked<() => string>; // string
type T3 = Unpacked<Promise<string>>; // string
type T4 = Unpacked<Promise<string>[]>; // Promise<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string
type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;
type T11 = Foo<{ a: string; b: number }>; // string | number
type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void }
? U
: never;
type T21 = Bar<{ a: (x: string) => void; b: (x: number) => void }>; // string & number
// Error, not supported
type ReturnType<T extends (...args: any[]) => infer R> = R;
type AnyFunction = (...args: any[]) => any;
type ReturnType<T extends AnyFunction> = T extends (...args: any[]) => infer R
? R
: any;
- initializing variables and members
- setting parameter default values
- determining function return types
let x = 3; // number
let s = 'a'; // string
const s = 'a'; // 'a'
const flags = [1, 2, 3] as const; // readonly [1, 2, 3]
let x = [0, 1, null]; // (number | null)[]
const zoo = [new Elephant(), new Snake()]; // (Elephant | Snake)[]
const zoo = [new Animal(), new Elephant(), new Snake()]; // Animal[]
window.onmousedown = function (mouseEvent) {
console.log(mouseEvent.button); // OK
console.log(mouseEvent.kangaroo); // Error!
};
interface Named {
name: string;
}
class Person {
name!: string;
}
// OK, because of structural typing
const p: Named = new Person();
const x = (a: number) => 0;
const y = (a: number, s: string) => 0;
y = x; // OK
x = y; // Error
const x = (a: number | string) => 0;
const y = (a: number) => 0;
y = x; // OK
x = y; // Error
const x = () => ({ name: 'Alice' });
const y = () => ({ name: 'Alice', location: 'Seattle' });
x = y; // OK
y = x; // Error
When a function has overloads, each overload in the source type must be matched by a compatible signature on the target type. This ensures that the target function can be called in all the same situations as the source function.
When comparing the types of function parameters, assignment succeeds if either the source parameter is assignable to the target parameter, or vice versa.
enum EventType {
Mouse,
Keyboard,
}
interface Event {
timestamp: number;
}
interface MouseEvent extends Event {
x: number;
y: number;
}
interface KeyEvent extends Event {
keyCode: number;
}
function listenEvent(
eventType: EventType,
// target:
handler: (n: Event) => void
) {
// ...
}
// Unsound, but useful and common
listenEvent(
EventType.Mouse,
// source:
(e: MouseEvent) => console.log(e.x + ',' + e.y)
);
Note: To raise errors when this happens, turn on the compiler flag
--strictFunctionTypes
.
When comparing functions for compatibility, optional and required parameters are interchangeable.
Extra optional parameters of the source type are not an error, and optional parameters of the target type without corresponding parameters in the source type are not an error.
When a function has a rest parameter, it is treated as if it were an infinite series of optional parameters.
function invokeLater(args: any[], callback: (...args: any[]) => void) {
/* ... Invoke callback with 'args' ... */
}
// Unsound - invokeLater "might" provide any number of arguments
invokeLater([1, 2], (x, y) => console.log(x + ', ' + y));
Enums are compatible with numbers, and numbers are compatible with enums. Enum values from different enum types are considered incompatible.
static
members and constructor
s do not affect compatibility. private
and protected
members in a class affect their compatibility.
Type parameters only affect the resulting type when consumed as part of the type of a member.
interface Empty<T> {}
declare let x: Empty<number>;
declare let y: Empty<string>;
x = y; // OK
interface NotEmpty<T> {
data: T;
}
declare let x: NotEmpty<number>;
declare let y: NotEmpty<string>;
x = y; // Error!
In TypeScript, there are two kinds of compatibility: subtype and assignment. These differ only in that assignment extends subtype compatibility with rules to allow assignment to and from any
, and to and from enum
with corresponding numeric values.