Simple types
const a: string = 'a string'
const b: number = 5
const c: boolean = true
const d: string[] = ['a', 'b', 'c']
const e: number[] = [1, 2, 3]
Simple types
const a = 'a string'
const b = 5
const c = true
const d = ['a', 'b', 'c']
const e = [1, 2, 3]
Types
type StrinOrNumber = string | number
type StringOrNumberOrBoolean = StringOrNumber | boolean
Modifiers
const a?: string // string | undefined
const obj = {
a: string,
b?: number,
}
funntion log(msg?: string) {}
class {
b!: string
}
obj!.one!.two!.three
Interfaces
interface Pizza {
slices: number
round: boolean
name: string
}
const pizza: Pizza = {
slices: 4,
round: true,
name: 'Hawaii'
}
Interfaces
interface Pizza {
slices: 4 | 8
round: boolean
name: 'Margherita' | 'Parmigiana'
}
const pizza: Pizza = {
slices: -1, // ❌
round: true,
name: 'Margherita'
}
Interfaces
interface A {
one: string
}
interface B extends A {
two: number
three?: string
}
Interfaces
interface Project {
name: string,
paymentBasis: 'fix' | 'hour'
}
interface GoodProject extends Project {
paymentBasis: 'hour'
}
Functions
function add(x: number, y: number): number {
return x + y
}
const add = (x: number, y: number): number => {
return x + y
}
function choose<T, P>(a: T, b: P): T | P {
if (something)
return a
else
return b
}
const waitForMe = async (text:string): Promise<string> => text
Overloading
function add(x: number): number
function add(x: number, y: number = 1): number {
return x + y
}
function add(a: string, b: string): string
function add(a: number, b: number): number
function add(a: string | number, b: string | number): string | number {
if (a.constructor.name === 'String') return a + b
else return a + b
}
Classes
class User {
private id: string
public readonly username: string
friends: User[] = []
constructor() {
this.id = 'abc'
this.username = 'John Doe'
}
private doSomeStuff() {}
}
Generics
function choose<T, P>(a: T, b: P): T | P {
if (something)
return a
else
return b
}
type Something<T> = {
a: string,
b: T
}
Typescript Utilities Types
- Partial & Required
- Readonly
- Pick & Omit
Partial / Required
interface Props {
a: number;
b: string;
};
const x: Props = { a: 5 }; // Error: property 'b' missing
const y: Partial<Props> = { a: 5 }; // OK
interface Props {
a?: number;
b?: string;
};
const x: Props = { a: 5 }; // OK
const y: Required<Props> = { a: 5 }; // Error: property 'b' missing
Readonly
interface Todo {
title: string;
}
const todo: Readonly<Todo> = {
title: 'Delete inactive users',
};
todo.title = 'Hello'; // Error: cannot reassign a readonly property
Pick / Omit
interface Todo {
title: string;
description: string;
completed: boolean;
}
const todo: Pick<Todo, 'title' | 'completed'> = {
title: 'Clean room',
completed: false,
};
interface Todo {
title: string;
description: string;
completed: boolean;
}
const todo: Omit<Todo, 'description'> = {
title: 'Clean room',
completed: false,
};
Also there are...
- Exclude & Extract
- Record
- NonNullable
- ReturnType
- InstanceType
- ThisType
Constructor
class Auto {
wheels: number
constructor(wheel: number) {
this.wheels = wheel
}
}
class Auto {
constructor(private wheel: number) { }
}
class Auto {
wheels!: number
constructor(init: typeof Auto) {
Object.assign(this, init)
}
}
Advanced Stuff
class Auto {
wheels?: number
doors?: number
// ...
constructor(init: Partial<Auto>) {
Object.assign(this, init)
}
}
const a = new Auto({
doors: 5,
})
Advanced Stuff
import { NonFunctionKeys } from 'utility-types'
class Auto {
wheels!: number
doors?: number
// ...
constructor(init: Pick<Auto, NonFunctionKeys<Auto>>) {
Object.assign(this, init)
}
}
const a = new Auto({
wheels: 4, // Required
doors: 5, // Optional
})
React Higher Order Components
function withLayout<P extends object>(WrappedComponent: React.ComponentType<P>) {
return (props: P) => (
<div id='app'>
<Header/>
<WrappedComponent {...props}/>
<Footer/>
</div>
);
}
Return Type depends on argument
function choose<T, P>(a: T, b: P, takeFirst?: false): P;
function choose<T, P>(a: T, b: P, takeFirst: true): T;
function choose<T, P>(a: T, b: P, takeFirst?: boolean): T | P {
if (takeFirst)
return a
else
return b
}
choose('a', 1).includes('a'); // error
choose('a', 1) * 2;
choose('a', 1, true) * 2; // error
choose('a', 1, true).includes('a');
Argument type depends on another argument
function checkLock<
B extends { alwaysUnlocked?: boolean },
O extends (B extends { alwaysUnlocked: true } ? {} : { locked: boolean })
>(obj: O, options?: B) {
return options && options.alwaysUnlocked || !obj.locked
}
checkLock({}); // error
checkLock({ locked: false });
checkLock({ locked: true, foo: 'bar' });
checkLock({}, { alwaysUnlocked: false }); // error
checkLock({}, { alwaysUnlocked: true });
checkLock({ foo: 'bar' }, { alwaysUnlocked: true });
Discriminated unions
type ColumnProps = {
width: number;
autoCalculateWidth?: false;
} | {
width?: undefined;
autoCalculateWidth: true;
}
// error
const props: ColumnProps = {
width: 4,
autoCalculateWidth: true,
};
Merging Objects together
function merge<O, T>(data1: O, data2: T): O | T {
return {
...data1,
...data2,
};
}
type Merge<O, T> = Omit<O, keyof T> & T;
function merge<O, T>(data1: O, data2: T): Merge<O, T> {
return {
...data1,
...data2,
};
}
Distinguish union types
interface TypeA {
type: 'A';
foo: string;
bar: string;
}
interface TypeB {
type: 'B';
foo: number;
blubb: string;
}
function distinguishType(type: TypeA | TypeB) {
if (type.type === 'A') {
type.foo.includes('whatever');
type.bar;
} else {
type.blubb.includes('something');
type.foo.toFixed(2);
}
}
The agony of Object.keys
const someObj = {
a: 'a',
b: 'b',
2: 'c',
};
function filterByKeys(keys: (keyof typeof someObj)[]) {
keys.map(key => someObj[key]);
}
filterByKeys(Object.keys(someObj)); // error
function getKeysFromObject<
T extends {}, K extends string = (keyof T & string)
>(object: T): K[] {
const keyPredicate = (key: string): key is K =>
object.hasOwnProperty(key) && typeof object[key] !== 'undefined' && object[key] !== null;
return Object
.keys(object)
.filter<K>(keyPredicate);
};
about predicates
interface SomeInterface {
what: string,
you: string,
}
const someArray: (SomeInterface | null)[] = [
{ what: 'ever', you: 'like' },
null,
{ what: 'the fuck', you: 'want' },
null,
];
// error: Signature ... must be a type predicate ?!
const filteredArray: SomeInterface[] = someArray.filter<SomeInterface>(value => !!value);
const predicate = (value: SomeInterface | null): value is SomeInterface => !!value;
const workingArray: SomeInterface[] = someArray.filter(predicate);
Back to Object.keys
const someObj = {
a: 'a',
b: 'b',
2: 'c',
};
function filterByKeys(keys: (keyof typeof someObj)[]) {
keys.map(key => someObj[key]);
}
function getKeysFromObject<
T extends {}, K extends string = (keyof T & string)
>(object: T): K[] {
const keyPredicate = (key: string): key is K =>
object.hasOwnProperty(key) && typeof object[key] !== 'undefined' && object[key] !== null;
return Object
.keys(object)
.filter<K>(keyPredicate);
};
filterByKeys(getKeysFromObject<typeof someObj>(someObj));
Tradeoffs
function getKeysFromObject<
T extends {}, K extends string = (keyof T & string)
>(object: T): K[] {
const keyPredicate = (key: string): key is K =>
object.hasOwnProperty(key) && typeof object[key] !== 'undefined' && object[key] !== null;
return Object
.keys(object)
.filter<K>(keyPredicate);
};
const testObject = {
2: 'teststring',
'testkey': 2131,
'otherkey': null,
3: null,
};
getKeysFromObject(testObject); // ('testkey' | 'otherkey')[] --- :(
getKeysFromObject<typeof testObject, '2' | 'testkey'>(testObject); // meh...