The TypeScript Masterclass NG-BE Workshop December 2021 @ddprrt - fettblog.eu - typescript-book.com
A presentation at NG-BE 2021 in December 2021 in Ghent, Belgium by Stefan Baumgartner
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.