Typescript

  • What is typescript?
  • History & Today
  • Why?
  • Basics
  • Typescript Utilities
  • Examples
  • Future

Javascript


							let a = 'a string'
							a = 5
							a = [1, 'two', {three: true}]
						

Typescript


								let a = 'a string' // a is a string now
								a = 5 // ❌
								a = [1, 'two', {three: true}] // ❌
							

TS Config


						npm i typescript
						tsc --init
					

General

  • target (ES2014, ..., ES2019, ESNEXT, ...)
  • module (commonjs, es2016, esnext, ...)
  • jsx
  • esModuleInterop
  • allowSyntheticDefaultImports
  • experimentalDecorators
  • emitDecoratorMetadata

Type Cheking

  • noImplicitAny
  • strictNullChecks
  • strictFunctionTypes
  • strictBindCallApply
  • strictPropertyInitialization
  • noImplicitThis
  • alwaysStrict
  • noImplicitReturns
  • noFallthroughCasesInSwitch

Production

  • noUnusedLocals
  • noUnusedParameters
  • removeComments

Basics

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

Examples

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


						type RequireSome<T, K extends keyof T> = {
							[X in Exclude<keyof T, K>]?: T[X]
						} & {
							[P in K]-?: T[P]
						}

						class Auto {
							wheels!: number
							doors?: number
							// ...
						
							constructor(init: RequireSome<Auto, 'wheels'>) {
								Object.assign(this, init)
							}
						}

						const a = new Auto({
							wheels: 4,
							doors: 5,
						})
					

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 });
					

Options exclude each other


						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...
					

Future

Optional Chaining

TS 3.7

						a && a.b && a.b.c // 🤬
					

						a?.b?.c // 🚀
					

Null Coalescing

TS 3.7

						something || 'default'
					

						false || 'default'      // => 'default'
						0     || 'default'      // => 'default'
					

						something ?? 'default'  // 🚀
					

						0     ?? 'default'      // => 0
						false ?? 'default'      // => false
					

						(something === null || something === undefined) ? something : 'default'