The TypeScript Masterclass NG-BE Workshop December 2021 @ddprrt - fettblog.eu - typescript-book.com

@ddprrt

What about you? Experience, Excitement, Expectations

Workshop Agenda ⭐ Part 1: Setting the scene ⭐ Part 2: Deep Dive Type System ⭐ Part 3: Generics and Conditional Types ⭐ Part 4: Advanced Types ⭐ Part 5: Methodologies

Setting the Scene Part 1 @ddprrt - fettblog.eu - typescript-book.com

Type Hierarchy in JavaScript https://exploringjs.com/impatient-js/ch_values.html

Type Hierarchy in TypeScript any / unknown Number String Enum Enum Symbol Boolean Class Unique null undefined never Object Union Array Intersection Tuple Function Conditional

Type Hierarchy in TypeScript any / unknown Number Enum primitive String Symbol Enum Unique Boolean Class null undefined never Object Union Array Intersection Tuple Function Conditional

Type Hierarchy in TypeScript any / unknown Number Enum primitive String Symbol Enum Unique Boolean Class null undefined never Object Union compound Array Intersection Tuple Function Conditional

TypeScript is a superset of JavaScript TS JS

1999 1996 1995 1997 2009 2003 2016 2017 2018 2015 ECMAScript 6 / ES2015

1999 1996 1995 1997 2016 2017 2018 2009 2003 2011 TypeScript’s inception 2015 ECMAScript 6 / ES2015

Anders Hejlsberg

https://channel9.msdn.com/Shows/Going+Deep/Anders-Hejlsberg-and-Lars-Bak-TypeScript-JavaScript-and-Dart

It’s not that JavaScript has no type system. There was just no way to formalize it

It’s not that JavaScript has no type system. There was just no way to formalize it Anders Hejlsberg

Red squiggly lines

⭐ Open Source and Open Development ” Closely track ECMAScript standard # Innovate in type system $ Best of breed tooling ⏬ Continually lower barrier to entry & Community, community, community

⭐ Open Source and Open Development ” Closely track ECMAScript standard # Innovate in type system $ Best of breed tooling ⏬ Continually lower barrier to entry & Community, community, community

⭐ TypeScript IS JavaScript ⭐ Language innovation through ECMAScript ⭐ Type system innovation through use cases ⭐ Tooling as prime citizen Non-goal: Apply a sound or “provably correct” type system. Instead, strike a balance between correctness and productivity.

⛩ Gradual, structural, generic ( Distinct value / type namespaces ) Extensive type inference * Control flow analysis & Object-oriented and functional

Tooling

TypeScript JavaScript

TypeScript Code emitting TS Type System Modern JS BrowserJS

This is a lot easier to handle Type System Modern JS BrowserJS

typescript files type checker compiler .ts, .tsx javascript files .js type definitions .d.ts

typescript extra tool ⭐ Type checking $ Bundling ⭐ Type erasure $ Tree shaking ⭐ Transpilation $ Minifications ⭐ JSX Tranformations ⭐ Module resolution

ambient type declarations .ts .ts .ts .ts .ts .ts Your codebase

ambient type declarations .ts .ts Ambient declarations .ts “window” “Object” .ts .ts .ts Your codebase

Type System Deep Dive Part 2 @ddprrt - fettblog.eu - typescript-book.com

⛩ Gradual, structural, generic ( Distinct value / type namespaces + ) Extensive type inference * Control flow analysis + & Object-oriented and functional

Type namespaces

primitive types number Symbol boolean Object undefined null string

top types any unknown number Symbol boolean Object undefined null string

bottom types never number Symbol boolean Object undefined null string

primitive types number Symbol boolean Object undefined null string

primitive types number boolean undefined null string

number value types … -1 boolean NaN 1000000 6 120.3 true false string … undefined null ‘Hello world’ ‘Baumi’

value types … -1 NaN 1000000 6 120.3 true false … undefined null ‘Hello world’ ‘Baumi’

number value types … -1 boolean NaN 1000000 6 120.3 true false string … undefined null ‘Hello world’ ‘Baumi’

number | string | undefined number … -1 boolean NaN 1000000 6 120.3 true false string … undefined null ‘Hello world’ ‘Baumi’

Intersection types { a: string } { } { b: string } a: string, b: string

Type Hierarchy in TypeScript any / unknown Number String Enum Enum Symbol Boolean Class Unique null undefined never Object Union Array Intersection Tuple Function Conditional

Type Hierarchy in TypeScript any / unknown Number String Enum Enum Symbol Boolean Class Unique Super Object Union Array Intersection Tuple Function Conditional null undefined never Sub

Type Hierarchy in TypeScript any / unknown Top Number String Enum Enum Symbol Boolean Class Unique Super Object Union Array Intersection Tuple Function Conditional null undefined Bottom never Sub

Type Hierarchy in TypeScript any / unknown Number String Enum Enum Symbol Boolean Class Unique Object Union Array Intersection Tuple Function Conditional null String template literal types Variadic tuple types undefined never void

any unknown The Wildcard The Cautious type Type safety is in the developer’s responsibility Type safety is in the compiler’s responsibility

unknown any absorbs everything In an intersection everything absorbs unknown type T00 = unknown & null; /” null type T01 = unknown & undefined; /” undefined type T02 = unknown & null & undefined; /” null & undefined (which becomes never) type T03 = unknown & string; /” string type T04 = unknown & string[]; /” string[] type T05 = unknown & unknown; /” unknown type T06 = unknown & any; /” any In a union an unknown absorbs everything type T10 = unknown | null; /” unknown type T11 = unknown | undefined; /” unknown type T12 = unknown | null | undefined; /” unknown type T13 = unknown | string; /” unknown type T14 = unknown | string[]; /” unknown type T15 = unknown | unknown; /” unknown type T16 = unknown | any; /” any

Dealing with any noImplicitAny: Make every any explicit. Easier to scan and find Type assertion: as any can deactivate type safety on short time The as keyword helps you to scan and find situations like this When in doubt: use unknown

Array Tuple variable length fixed element type* fixed length variable element type Each element has the same type * which can be broad! Each position has a defined type Good for collections Good for destructuring + renaming

Arrays vs Tuples const person = [39, “Stefan”]; const [age, name] = person; const person: [number, string] = [39, “Stefan”]; const [age, name] = person;

Arrays vs Tuples const person = [39, “Stefan”]; person: (string | number)[] const [age, name] = person; const person: [number, string] = [39, “Stefan”]; const [age, name] = person;

Arrays vs Tuples const person = [39, “Stefan”]; const [age, name] = person; person: (string | number)[] age: string | number, name: string | number const person: [number, string] = [39, “Stefan”]; const [age, name] = person;

Arrays vs Tuples const person = [39, “Stefan”]; const [age, name] = person; person: (string | number)[] age: string | number, name: string | number const person: [number, string] = [39, “Stefan”]; const [age, name] = person; person: [number, string]

Arrays vs Tuples const person = [39, “Stefan”]; const [age, name] = person; person: (string | number)[] age: string | number, name: string | number const person: [number, string] = [39, “Stefan”]; const [age, name] = person; age: number, name: string person: [number, string]

Arrays vs Tuples const person = [39, “Stefan”]; const [age, name] = person; person: (string | number)[] age: string | number, name: string | number const person: [number, string] = [39, “Stefan”]; const [age, name] = person; age: number, name: string const person = [39, “Stefan”] as const; person: [number, string]

Never The never type is for situations that can never happen This means that you end up in a situation where TypeScript thinks your program shouldn’t be reachable E.g. after throwing an error, when exhausting all options in a union Never is really handy for complex types

Control flow analysis

Control flow analysis Control flow happens on conditions and their branches if-statements / switch-case statements TypeScript can narrow down a type based on the position of a value in the control flow You can help narrowing by typeof, instanceof, “prop” in checks Type predicates help you with elaborate conditions

Control flow analysis Narrowing • Using typeof guards • Using instance of guards • Truthiness (excludes null and undefined) • Discriminated unions • Type predicates

Control flow analysis Typeof function padLeft(padding: number | string, input: string) { if (typeof padding ==% “number”) { return ” “.repeat(padding) + input; padding: number } return padding + input; padding: string }

Control flow analysis Assignments function example() { let x: string | number | boolean; x = Math.random() < 0.5; console.log(x); if (Math.random() < 0.5) { x = “hello”; console.log(x); } else { x = 100; console.log(x); } return x; }

Control flow analysis Assignments function example() { let x: string | number | boolean; x = Math.random() < 0.5; x: boolean console.log(x); if (Math.random() < 0.5) { x = “hello”; console.log(x); } else { x = 100; console.log(x); } return x; }

Control flow analysis Assignments function example() { let x: string | number | boolean; x = Math.random() < 0.5; x: boolean console.log(x); if (Math.random() < 0.5) { x = “hello”; x: string console.log(x); } else { x = 100; console.log(x); } return x; }

Control flow analysis Assignments function example() { let x: string | number | boolean; x = Math.random() < 0.5; x: boolean console.log(x); if (Math.random() < 0.5) { x = “hello”; x: string console.log(x); } else { x: number x = 100; console.log(x); } return x; }

Control flow analysis Assignments function example() { let x: string | number | boolean; x = Math.random() < 0.5; x: boolean console.log(x); if (Math.random() < 0.5) { x = “hello”; x: string console.log(x); } else { x: number x = 100; console.log(x); } x: number | string return x; }

Control flow analysis Type predicates type Dice = 1 | 2 | 3 | 4 | 5 | 6; function isDice(input: number): input is Dice { return [1, 2, 3, 4, 5, 6].includes(input) } function rollDice(input: number) { if(isDice(input)) { console.log(“Rolling..(“) } } Every function that returns a boolean can change the type of a value

Control flow analysis Type predicates type Dice = 1 | 2 | 3 | 4 | 5 | 6; function isDice(input: number): input is Dice { return [1, 2, 3, 4, 5, 6].includes(input) } function rollDice(input: number) { if(isDice(input)) { console.log(“Rolling..(“) } } input: number Every function that returns a boolean can change the type of a value

Control flow analysis Type predicates type Dice = 1 | 2 | 3 | 4 | 5 | 6; function isDice(input: number): input is Dice { return [1, 2, 3, 4, 5, 6].includes(input) } input: number function rollDice(input: number) { if(isDice(input)) { input: Dice console.log(“Rolling..(“) } } Every function that returns a boolean can change the type of a value

Unions Intersections Unions widen the set of possible values Intersections narrow the set of possible values Unions of objects can allow for more values than defined Similar to extends in interfaces Discriminated unions help It’s possible to narrow to never

Discriminated Union types Circle Square without kind type Shape = | { kind: “circle”; radius: number } | { kind: “square”; x: number } | { kind: “triangle”; x: number; y: number }; function area(s: Shape) { if (s.kind ==% “circle”) { return Math.PI * s.radius * s.radius; } else if (s.kind ==% “square”) { return s.x * s.x; } else { return (s.x * s.y) / 2; } } Triangle Circle Square with kind Triangle By setting specific properties to literal types (value types), we can specifically pin point to a subset of a union

Structural typing

nominal structural The name is important The shape is important Type User from lib A and type Person from lib B can look the same, but are incompatible Even with no relation at all types are compatible as long as the structure is compatible Subtypes are allowed

Why is TypeScript structurally typed? • A structural type system is the most fitting way to describe dynamically typed language scenarios without too much overhead • JavaScript relies a lot on object literals. • Graduality: There is still a way to use TypeScript without a single type annotation, just pure JavaScript • There have been approaches to add nominal types. There are even some nominal types hidden within TypeScript

interface type Contract to the outside Contract in your codebase Can be modified from users Allows for advanced type system features

Example: Union and Intersection types Toy Shop • In this example, we model data for a possible toy shop • We define a set of features every Toy should have • We create specialized versions of each Toy • We used literal types (value types) to create discriminated unions • Create a printToy function so we can see what

Task: Union and Intersection Types A Serverless function • Define the interface to a serverless function handler • It can handle three types: • HTTP requests, Queue events, Time based events • Their events have some common properties • Look at the interfaces in the example and try to create a type based model • Do we need kind properties or are we discriminated enough? https://tsplay.dev/w14QYW

Generics & Conditional Types Part 3 @ddprrt - fettblog.eu - typescript-book.com

Generics

Generics Generic type parameters function isAvailable<Formats>( obj: Formats, key: string | number | symbol ): key is keyof Formats { return key in obj; } function isFormatVailable( obj: VideoFormatURLs, key: string ): key is keyof VideoFormatURLs { return key in obj; } function isSubtitleAvailable( obj: SubtitleURLs, key: string ): key is keyof SubtitleURLs { return key in obj; } We call everything within <> generic type parameters Both functions to the same and have a similar signature. We just implement both for the sake of types

Generics Generic annotations vs inference Take a function function identity<T>(arg: T): T { return arg; } what’s the difference between const z = identity<string>(“TypeScript”); and const y = identity(“TypeScript”); Generics can be annotated or inferred, just like regular types Inferred generic types are narrowed to a literal type —> helpful in many scenarios

Generics Generic type constraints function isAvailable<FormatList extends object>( obj: FormatList, key: string ): key is keyof FormatList { return key in obj; } We can limited the possible shapes a generic type can take by applying constraints or “boundaries”

Generics Related type parameters type URLList = { [k: string]: URL; }; function loadFile<Formats extends URLList>(fileFormats: Formats, format: keyof Formats) { /” The real work ahead } We can create relations between generic type parameters to allow for more explicit binding function loadFile2<Formats extends URLList, Keys extends keyof Formats>( fileFormats: Formats, format: Keys ) { /” The real work ahead }

Conditional types

Function overloads vs Conditional types

Advanced Types Part 4 @ddprrt - fettblog.eu - typescript-book.com

Promises and async/await

String template literal types

Task: String Template Literal Types A Format function • Create types for a format function • The format function expects a string with placeholders. • Placeholders are denoted with curly braces, e.g {placeholder} • The second argument is an object with the correct placeholder keys • Bonus • Add optional primitive type parameters, e.g {placeholder:number} • Escape curlies • We only need types, you don’t need to implement the function https://tsplay.dev/mx6zxw

Variadic Tuple Types

Task: Variadic Tuple Types Promisify • Create a promisify function • It accepts a function of any shape, as long as the last argument is a callback function • It returns a function that has the same shape without the last argument • If you call the returning function, you get a Promise that eventually resolves to the original callback’s https://tsplay.dev/WzLBkN

Methodologies & Gotchas Part 5 @ddprrt - fettblog.eu - typescript-book.com

Low maintenance types

Accumulator technique

Void

The problem with Enums

Error Handling Gotchas

Unexpected Intersections

Thank you.