Slide 1

Slide 1 text

TypeScript for JS Developers Jussi Pohjolainen

Slide 2

Slide 2 text

Motivation: TypeScript • JavaScript has several problems • Developed in 10 days, not meant for the type of programming that we today have - How to fix? • TypeScript has several advantages • You would have type safety in your code, APIs and Libraries • getUserData("I will break you") • You would not have "null pointer exceptions" • const username = null; console.log(username.length) • You would have autocompletion • You could define your own types for even better type safety

Slide 3

Slide 3 text

TypeScript • Open Source Journey • TypeScript was released as an open source project on GitHub from the beginning • This decision helped the language evolve rapidly due to community contributions and feedback. • Adoption by major frameworks • Evolution through Version

Slide 4

Slide 4 text

Overview • Superset of JavaScript: • TypeScript extends JavaScript by adding additional syntax and features. • Any valid JavaScript code is also valid TypeScript code • You can gradually adopt TypeScript in existing projects. • Static Typing: • One of the core features of TypeScript is its optional static typing system. • Compile-time Type Checking • Cross-platform and Cross-browser Compatibility • Popularity Among Developers

Slide 5

Slide 5 text

TS popularity

Slide 6

Slide 6 text

Advantages to JS • Static Typing • Lot's of additional types! • Advanced OO features (more like java) • Tooling • Provides backend of language service which IDE can use • VS Code: Intellisense, Type checking, Refactor, Debugging • Compilation • In React you can also use • tsc just for type checking • babel/swc transforms ts to js • => faster compile

Slide 7

Slide 7 text

Year ECMAScript Version TypeScript Version ECMAScript Features TypeScript Features 1997 ES1 - Basic features: types, syntax, operators, objects - 1998 ES2 - Minor editorial changes - 1999 ES3 - Regular Expressions, try/catch, tighter specification of errors, more built-in functions - 2009 ES5 - "Strict mode," JSON support, Array enhancements (forEach, map, filter, reduce, etc.), accessor properties - 2012 - TypeScript 0.8 - Initial release, basic typing, interfaces, classes, modules 2014 - TypeScript 1.0 - Official stable release, annotations for shapes of JavaScript objects 2015 ES6/ES2015 TypeScript 1.5 Arrow functions, Promises, Classes, Modules, Template literals, Destructuring, Default parameters Decorators, ES6 module compilation 2016 ES7/ES2016 TypeScript 2.0 Array.prototype.includes, Exponentiation Operator Null- and undefined-aware types, Control flow based type analysis, Discriminated unions 2017 ES8/ES2017 TypeScript 2.1 Async/await, Object.entries(), Object.values(), String padding, Trailing commas Keyof and lookup types, Mapped types, Object rest and spread 2018 ES9/ES2018 TypeScript 3.0 Asynchronous iteration, Promise finally, Rest/Spread Properties Project references, Tuples in rest parameters and spread expressions 2019 ES10/ES2019 TypeScript 3.5 Array.prototype.{flat,flatMap}, Object.fromEntries, Optional catch binding Omit helper type, Improved excess property checks in union types 2020 ES11/ES2020 TypeScript 4.0 BigInt, Dynamic import, Nullish coalescing Operator, Optional Chaining Variadic tuple types, Labeled tuple elements, Class property inference from constructors, Short-circuiting assignment operators (??=, &&=, ||=) 2021 ES12/ES2021 TypeScript 4.1 String.prototype.replaceAll, Promise.any, Logical assignment operators Template literal types, Key remapping in mapped types, Recursive conditional types 2022 ES13/ES2022 TypeScript 4.9 top-level await. public and private fields. Object.hasOwn satisfies operator, auto-accessors in classes (proposal), improvements in type narrowing and checks 2023 ES14/ES2023 TypeScript 5.3 New array functions: toSorted, toReversed, ... Improved type narrowing, correctness checks and performance optimizations ES.Next TypeScript 5.4 Object.groupBy and Map.groupBy support

Slide 8

Slide 8 text

Compilers Feature/Aspect tsc (TypeScript Compiler) Babel SWC (Speedy Web Compiler) Primary Use Compiling TypeScript code to JavaScript. Transpiling modern JavaScript to older versions of JavaScript, and adding polyfills. Compiling TypeScript and modern JavaScript to older versions of JavaScript, focusing on performance. Alternative to Babel. Language Support TypeScript, JavaScript (as part of TypeScript files). JavaScript (ES6+), TypeScript (with plugins), React JSX, and other languages with plugins. JavaScript, TypeScript, JSX. Performance Moderate, optimized for correctness and language features rather than speed. Moderate, highly dependent on the plugins used. High, designed for fast compilation times. Ecosystem Compatibility Very good, widely used in TypeScript projects. Extremely high, with a vast array of plugins for various languages and proposals. Growing, tries to be compatible with Babel plugins Configuration Typically simpler for TypeScript projects, uses tsconfig.json. Highly configurable, uses .babelrc or babel.config.js, can become complex with plugins. Similar to Babel but aims to be simpler; uses swcrc. Output Customization Less flexible, primarily focuses on JavaScript versions (ES5, ES6, etc.). Highly flexible, can customize output extensively through plugins. Less flexible than Babel but more than tsc; aims at performance optimization. Community and Support Strong support from Microsoft, large community. Very large community, extensive documentation and community support. Smaller community, but growing quickly due to performance advantages. Typical Use Cases Ideal for projects that are primarily TypeScript. Suitable for projects that need specific transformations, polyfills, or support for experimental features. Best for projects requiring fast build times, especially in development environments like Next.js.

Slide 9

Slide 9 text

Workflow and Project NPM + ESlint + Prettier + TypeScript

Slide 10

Slide 10 text

NPM Project • NPM project for basis • npm init -y • TypeScript • npm install !--save-dev typescript • npx tsc --init • ESlint for linting • npm init @eslint/config@latest • Prettier for styling • npm install !--save-dev prettier eslint-config-prettier eslint- plugin-prettier

Slide 11

Slide 11 text

npx? • npx is a command-line utility that comes bundled with npm, the Node.js package manager, as of npm version 5.2.0 • Simplify the execution of binaries from Node modules and manage package executions • npx allows you to run commands from locally installed packages without having to explicitly add the package's binaries to your system path or run them from the ./node_modules/.bin directory • npx enables you to execute packages directly from the npm registry without having to install them globally on your system

Slide 12

Slide 12 text

npx tsc --init • Compiler Options: • These options tell the TypeScript compiler how to compile .ts files into .js files. • ES Target version, module system, strict mode, out directory etc • Include/Exclude Files • Type Checking Options • noImplicitAny, strictNullChecks, strictFunctionTypes • And others..

Slide 13

Slide 13 text

Example file { "compilerOptions": { "target": "es5", "module": "commonjs", "strict": true, "outDir": "./dist" }, "include": ["src/**/*"] !// every file all of it's nested directories } enables noImplicitAny, noImplicitThis, strictNullChecks, strictFunctionTypes, strictBindCallApply, alwaysStrict

Slide 14

Slide 14 text

noImplicitAny • TypeScript requires every variable, parameter, and return type to have a defined type. • Compile type error • If a type cannot be inferred and is not explicitly defined, the compiler will treat this as an error function multiply(a, b) { return a * b; } Will not work, you will have to define the types

Slide 15

Slide 15 text

strictNullChecks: true • TypeScript treats null and undefined as types that are distinct from all other types • This means that you cannot assign null or undefined to a variable unless its type explicitly includes null or undefined • Helps to avoid common bugs related to null reference errors by making sure that parts of your code that could potentially be null or undefined are explicitly checked. let user: { name: string; } | null; user = null; !// No error with strictNullChecks Without | null, this would not work

Slide 16

Slide 16 text

useUnknownInCatchVariables false try { !// Some operation that might throw } catch (error) { console.error(error.message); } true try { !// Some operation that might throw } catch (error) { console.error(error instanceof Error ? error.message : 'An unknown error occurred'); }

Slide 17

Slide 17 text

noimplicitThis Does not work function standaloneFunction() { console.log(this.someProperty); } const obj = { someProperty: 'Hello, TypeScript!', }; standaloneFunction.call(obj); Works, you will define "this" function standaloneFunction(this: { someProperty: string }) { console.log(this.someProperty); } const obj = { someProperty: 'Hello, TypeScript!', }; standaloneFunction.call(obj);

Slide 18

Slide 18 text

compile using locally installed typescript Output is ES5

Slide 19

Slide 19 text

ESLint • Quality tool • Customizable Rules • Plugin System • eslint-plugin-react, eslint-plugin-vue, @typescript-eslint/eslint-plugin • Automatic Code Fixing • Integration with Development Environments • npm init @eslint/config@latest

Slide 20

Slide 20 text

npm init @eslint/config@latest • Configures the ESLint • Installs also • globals: Supplies predefined global variables (e.g., process, module) for specific environments like Node.js. • @eslint/js: Contains the default JavaScript-specific rules and configurations for ESLint. • typescript-eslint: Adds TypeScript support to ESLint by bridging TypeScript- specific syntax and rules.

Slide 21

Slide 21 text

eslint.config.mjs export default [ { files: ["**/*.{js,mjs,cjs,ts}"], rules: { "no-var": "error", !// Disallow var "eqeqeq": "error", !// Enforce !!=== and !!!== "no-unused-vars": "warn", !// Warn on unused variables "prefer-const": "error", !// Suggest using const "no-else-return": "error", !// Disallow else after return "curly": "error", !// Enforce consistent brace style "no-multiple-empty-lines": ["error", { max: 1 }], !// Limit empty lines "camelcase": "error", !// Enforce camelCase naming "no-eval": "error", !// Disallow eval() }, You can add different rules for ESLint

Slide 22

Slide 22 text

Gives linting errors Tries to fix linting errors Got one linting error fixed, styling problems exists

Slide 23

Slide 23 text

Styling: Prettier • Opinionated code formatter that supports many languages and integrates with most editors. • It removes all original styling and ensures that all outputted code conforms to a consistent style. • To install with eslint support • npm install !--save-dev prettier eslint-config-prettier eslint-plugin-prettier

Slide 24

Slide 24 text

Plugins? • eslint-config-prettier • Configuration that disables ESLint rules that might conflict with Prettier. • Since both tools have the potential to enforce or correct code formatting, this configuration ensures that ESLint does not report errors for stylistic issues that Prettier will fix. • eslint-plugin-prettier • Turns Prettier rules into ESLint rules. • This plugin allows you to run Prettier as part of your ESLint configuration. • ESLint will give you an prettier styling error.

Slide 25

Slide 25 text

.mjs modification • Registration of Prettier as a plugin. • Activation of the prettier/prettier rule. • Integration of Prettier’s ESLint configuration to avoid conflicts. • Imports for eslint-plugin-prettier and eslint-config-prettier.

Slide 26

Slide 26 text

.mjs modification import prettierPlugin from "eslint-plugin-prettier"; import prettierConfig from "eslint-config-prettier"; plugins: { prettier: prettierPlugin }, "prettier/prettier": "error", prettierConfig,

Slide 27

Slide 27 text

ESLint will give Prettier errors

Slide 28

Slide 28 text

ESLint can fix using Prettier Notice the proper indentation

Slide 29

Slide 29 text

Prettier modification: .prettierrc { "semi": false, "singleQuote": true, "printWidth": 100, "tabWidth": 4, "useTabs": false, "trailingComma": "all", "bracketSpacing": true, "jsxBracketSameLine": false, "arrowParens": "avoid", "endOfLine": "lf" }

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

VS Code • VS Code has excellent support for TypeScript • VS Code also has extensions for ESLint and Prettier • You can configure VS Code so that it runs npx eslint !--fix everytime you save a file

Slide 32

Slide 32 text

VS Code: ESLint

Slide 33

Slide 33 text

Modify VS Code settings, fill run npx eslint fix when saving

Slide 34

Slide 34 text

VS Code: Displays ESLint problems You can now see the ESLint problems graphically

Slide 35

Slide 35 text

VS Code: after save Fixed some of the problems

Slide 36

Slide 36 text

npx tsc • Compile TS to JS using • npx tsc • By default it will compile to ES2016 • You can change the target

Slide 37

Slide 37 text

npx tsc TypeScript const PI: number = 3.14; console.log(PI); ES6 "use strict"; const PI = 3.14; console.log(PI);

Slide 38

Slide 38 text

Change target to ES5

Slide 39

Slide 39 text

npx tsc TypeScript const PI: number = 3.14; console.log(PI); ES5 "use strict"; var PI = 3.14; console.log(PI);

Slide 40

Slide 40 text

npx tsc !--watch Constantly compiling

Slide 41

Slide 41 text

Lab 01: Project Creation

Slide 42

Slide 42 text

Types

Slide 43

Slide 43 text

ECMAScript Types Primitive types • boolean • null • undefined • number • bigint • string • symbol Objects • Arrays • Functions • Dates, RegExp… • Custom objects

Slide 44

Slide 44 text

BigInt? • BigInt is a built-in object in JavaScript that provides a way to represent whole numbers larger than 2^53 – 1 • Largest number JavaScript can reliably represent with the Number primitive. • The BigInt type was introduced in ECMAScript 2020 (ES11) • const largeNumber1 = 9007199254740991n • const largeNumber2 = BigInt("9007199254740991");

Slide 45

Slide 45 text

Symbol? • Object keys can be strings or symbols • If you use number as a key it will be casted to string, number -> string • Symbol is a primitive data type introduced in ECMAScript 2015 (ES6) • Way to create unique identifiers for object properties • Each time you create a symbol, a new and unique symbol value is generated • Even if two symbols have the same description, they are considered distinct • Symbols are particularly useful when you want to add properties to objects without the risk of property name collisions

Slide 46

Slide 46 text

Problem !// Plugin A trying to add a role user.role = "admin"; !// Plugin B also trying to add a role, unintentionally overwrites Plugin A's role user.role = "editor";

Slide 47

Slide 47 text

Solution code let user = {} !// Plugin A adds a role using a symbol const pluginARoleKey = Symbol('role'); user[pluginARoleKey] = "admin"; !// Plugin B also adds a role, using its own symbol const pluginBRoleKey = Symbol('role'); user[pluginBRoleKey] = "editor"; console.log(user) output { [Symbol(role)]: 'admin', [Symbol(role)]: 'editor' }

Slide 48

Slide 48 text

Using const code let user = {} !// Plugin A adds a role using a symbol const pluginARoleKey = Symbol('role'); user[pluginARoleKey] = "admin"; !// Plugin B also adds a role, using its own symbol pluginARoleKey = Symbol('role'); user[pluginARoleKey] = "editor"; console.log(user) output /Users/pohjus/Desktop/temp/x/index.js:8 pluginARoleKey = Symbol('role'); ^ TypeError: Assignment to constant variable. const => you cannot declare it You cannot do this

Slide 49

Slide 49 text

Solution code let user = {} user[Symbol('role')] = "admin"; user[Symbol('role')] = "editor"; const symbols = Object.getOwnPropertySymbols(user); console.log(user[symbols[0]]); !// Outputs: "admin" console.log(user[symbols[1]]); !// Outputs: "editor" output admin editor

Slide 50

Slide 50 text

Example !// sharedSymbols.js export const pluginARoleKey = Symbol('role'); export const pluginBRoleKey = Symbol('role'); !// Plugin A import { pluginARoleKey } from './sharedSymbols.js'; let user = {}; user[pluginARoleKey] = "admin"; !// Plugin B import { pluginBRoleKey } from './sharedSymbols.js'; user[pluginBRoleKey] = "editor"; !// Somewhere else in the application import { pluginARoleKey, pluginBRoleKey } from './sharedSymbols.js'; console.log(user[pluginARoleKey]); !// Outputs: "admin" console.log(user[pluginBRoleKey]); !// Outputs: "editor"

Slide 51

Slide 51 text

Benefits • Uniqueness • Every symbol value returned by Symbol() is unique • Ensuring that property keys created with symbols do not clash with other property names or symbols in the object. • Privacy • While not truly private (since JavaScript does not have private properties in the traditional sense), symbols can simulate a form of privacy. Symbols are not enumerated by for...in loops or Object.keys() • Avoid Accidental Exposure: Since symbols are not included in the output of JSON.stringify(), using symbols can help avoid accidentally leaking sensitive information through logging or transmitting objects over a network.

Slide 52

Slide 52 text

Using prebuilt special symbol const collection = { items: ['item1', 'item2', 'item3'], [Symbol.iterator]() { let index = 0; return { next: () !=> ({ done: index !!=== this.items.length, value: this.items[index!++] }) }; } }; for (let item of collection) { console.log(item); !// Logs each item }

Slide 53

Slide 53 text

TypeScript Everyday Types

Slide 54

Slide 54 text

Everyday TypeScript types Primitive • any • unknown • never • void Objects • Enums • Tuples • Arrays • Function types

Slide 55

Slide 55 text

any vs unknown any let anything: any = "hello"; console.log(anything); anything = 42; console.log(anything + 10); unknown let uncertain: unknown = "hello"; !// console.log(uncertain.length) if (typeof uncertain !!=== "string") { console.log(uncertain.length); } You can change the type but to use the variable you musta check the type This is like “normal” JavaScript

Slide 56

Slide 56 text

Unknown usage !// Function to fetch a Chuck Norris joke async function fetchChuckNorrisJoke(): Promise { const response = await fetch("https:!//api.chucknorris.io/jokes/random"); const data: unknown = await response.json(); if ( typeof data !!=== "object" !&& data !!!== null !&& "value" in data !&& typeof data.value !!=== "string" ) { return data.value; } throw new Error("Invalid Chuck Norris joke data format"); } !// Example usage async function displayChuckNorrisJoke() { try { const joke = await fetchChuckNorrisJoke(); console.log(joke); } catch (error) { console.error("Error fetching Chuck Norris joke:", error); } } displayChuckNorrisJoke(); Promise

Slide 57

Slide 57 text

interface ChuckNorrisJokeResponse { value: string; } function isChuckNorrisJokeResponse( data: unknown, ): data is ChuckNorrisJokeResponse { return ( typeof data !!=== "object" !&& data !!!== null !&& "value" in data !&& typeof data.value !!=== "string" ); } !// Function to fetch a Chuck Norris joke async function fetchChuckNorrisJoke(): Promise { const response = await fetch("https:!//api.chucknorris.io/jokes/random"); const data: unknown = await response.json(); if (isChuckNorrisJokeResponse(data)) { return data.value; } throw new Error("Invalid Chuck Norris joke data format"); } !// Example usage async function displayChuckNorrisJoke() { try { const joke = await fetchChuckNorrisJoke(); console.log(joke); } catch (error) { console.error("Error fetching Chuck Norris joke:", error); } } displayChuckNorrisJoke(); if returns true, the given parameter will be the type of the interface

Slide 58

Slide 58 text

never Example 1 function error(message: string): never { throw new Error(message); } !// This function call doesn't return. It throws an exception. error('Fatal error’); Example 2 function forever(): never { while (true) { console.log(‘repeat’) } } forever();

Slide 59

Slide 59 text

void function logMessage(message: string): void { console.log(message); return; } logMessage("Hello, TypeScript!"); !// only undefined acceptable (or null under certain configurations) let unusable: void = undefined;

Slide 60

Slide 60 text

enum enum Direction { Up, Down, Left, Right } let move: Direction = Direction.Up; console.log(move); !// Outputs: 0 !// Changing the direction move = Direction.Right; console.log(move); !// Outputs: 3

Slide 61

Slide 61 text

enum enum ResponseStatus { Success = "SUCCESS", Failure = "FAILURE", Pending = "PENDING" } let response: ResponseStatus = ResponseStatus.Success; console.log(response); !// Outputs: "SUCCESS"

Slide 62

Slide 62 text

enum function getServerStatus(statusCode: number): ResponseStatus { switch (statusCode) { case 200: return ResponseStatus.Success; case 400: case 500: return ResponseStatus.Failure; default: return ResponseStatus.Pending; } } console.log(getServerStatus(200)); !// Outputs: "SUCCESS"

Slide 63

Slide 63 text

tuple let userInfo: [string, number]; userInfo = ["Alice", 42]; !// Correct console.log(userInfo); !// Outputs: ["Alice", 42] !// Accessing tuple elements console.log(userInfo[0]); !// Outputs: "Alice" console.log(userInfo[1]); !// Outputs: 42

Slide 64

Slide 64 text

Feature Tuple Array Definition A tuple allows you to express an array with a fixed number of elements whose types are known, but need not be the same. An array allows you to store a collection of elements of the same type. Type Safety Tuples are strict about both the number of elements and the type of each element at specific positions. Arrays are flexible with the number of elements and enforce type uniformity across all elements. Use Case Useful when you need to return multiple fields from a function or to model a fixed structure. Ideal for collections of items where operations like iteration are needed. Mutability Elements can be mutated unless they are explicitly set as readonly. However, the structure (length and types of elements) should remain constant. Elements can be added, removed, or changed, and the length can dynamically change during runtime. Syntax Declared with square brackets and types are separated by commas, e.g., [string, number]. Declared with type followed by square brackets, e.g., number[] or Array. Examples let userInfo: [string, number] = ["Alice", 30]; let scores: number[] = [85, 90, 93]; Flexibility Less flexible in terms of length manipulation as the size is fixed to a defined number of elements. More flexible in handling collections of any size, adjusting dynamically as needed. Methods Supports standard array methods but must conform to the defined tuple structure. Supports a wide range of array methods like map, filter, reduce, etc. Best Used For function returns, handling multiple return values, and fixed format data storage. For homogeneous data collections subject to dynamic changes in size or content.

Slide 65

Slide 65 text

tuple let complexTuple: [string, number, boolean?, !!...string[]]; complexTuple = ["Bob", 30]; !// Correct, boolean is optional complexTuple = ["Charlie", 25, true, "Developer", "New York"]; !// Correct !// Accessing the rest element console.log(complexTuple[3]); !// Outputs: "Developer"

Slide 66

Slide 66 text

tuple let [userName, userAge] = userInfo; console.log(userName); !// Outputs: "Alice" console.log(userAge); !// Outputs: 42

Slide 67

Slide 67 text

tuple function getUser(): [string, number] { let name = "Diana"; let age = 27; return [name, age]; } let [name, age] = getUser(); console.log(name); !// Outputs: "Diana" console.log(age); !// Outputs: 27

Slide 68

Slide 68 text

tuple type UserRow = [string, number, boolean]; function parseCSV(data: string): UserRow[] { return data.split("\n").map(row !=> { const [name, age, isActive] = row.split(","); return [name.trim(), parseInt(age), isActive.trim() !!=== "true"]; }); } const csvData = `"John Doe",30,true\n"Jane Smith",25,false`; const users = parseCSV(csvData); console.log(users); !// Outputs: [["John Doe", 30, true], ["Jane Smith", 25, false]]

Slide 69

Slide 69 text

array let names: string[] = ["Alice", "Bob", "Charlie"]; console.log(names); !// Outputs: ["Alice", "Bob", "Charlie"] !// Adding a new name to the array names.push("Diana"); console.log(names); !// Outputs: ["Alice", "Bob", "Charlie", "Diana"]

Slide 70

Slide 70 text

array, using generic type let ages: Array = [25, 30, 35]; console.log(ages); !// Outputs: [25, 30, 35] !// Adding a new age to the array ages.push(40); console.log(ages); !// Outputs: [25, 30, 35, 40]

Slide 71

Slide 71 text

array and tuple let coordinates: [number, number][] = [[0, 0], [10, 10], [20, 20]]; console.log(coordinates); !// Outputs: [[0, 0], [10, 10], [20, 20]] !// Adding a new coordinate pair to the array coordinates.push([30, 30]); console.log(coordinates); !// Outputs: [[0, 0], [10, 10], [20, 20], [30, 30]]

Slide 72

Slide 72 text

Functions and Type Guards

Slide 73

Slide 73 text

Functions • Basic Functions • Void • Callbacks • TypeGuards

Slide 74

Slide 74 text

function sum1(a: number, b: number): number { return a + b } function sum2(a: number, b: number) { return a + b } function sum3(a: number, b: number) : void { console.log(a + b) } function sum4(a: number, b: number) : void { console.log(a + b) return undefined } function sum5(a: number, b: number) : void { console.log(a + b) return; } TS figures that return will be number Does not return anything Well void means undefined

Slide 75

Slide 75 text

const sum = (a: number, b: number) !=> a + b const extract = (a: number, b: number) !=> a - b let f1 : any f1 = sum f1 = extract f1 = "hello" let f2: Function f2 = sum f2 = extract f2 = () !=> console.log("hello") let f3: (a: number, b: number) !=> number f3 = sum f3 = extract Functions are objects You can put any function You can put only function that accepts two numbers and returns a number You can put here anything you want

Slide 76

Slide 76 text

Type Guards

Slide 77

Slide 77 text

Type Guards, just like in JS • typeof • returns string of the type • instanceof • if an object has in its prototype chain the prototype property of a constructor. • in • Check if property exists

Slide 78

Slide 78 text

function doSomething(input: number | string): void { if (typeof input !!=== "string") { console.log("Input is a string:", input.toUpperCase()); }!!... } function handleEntity(animal: Animal): void { if (animal instanceof Tiger) { } !!... } function move(animal: Bird | Fish) { if ('fly' in animal) { console.log("It's a bird!"); animal.fly(); } }

Slide 79

Slide 79 text

type • Type alias is a way to create a new name for a type • Can include primitive types, union types, object types, function types, and more. • Type aliases provide a convenient way to refer to complex types and improve code readability and maintainability.

Slide 80

Slide 80 text

Type: Simple Examples type Person = { name: string; age: number; }; const person: Person = { name: "Alice", age: 30 }; type ID = string | number; const userId: ID = "12345"; const orderId: ID = 67890; type Greet = (name: string) !=> string; const greet: Greet = (name: string) !=> `Hello, ${name}!`;

Slide 81

Slide 81 text

typeof: reference to a type const john = { name: "john", age: 24, }; !// Use typeof to reference the type of the `person` variable type Person = typeof john; !// Must have same structure !// { name: string, age: number } const jack: Person = { name: "jack", age: 80 }; console.log(jack.name);

Slide 82

Slide 82 text

keyof type Person = { name: string; age: number; }; !// "name" | "age" type PersonKeys = keyof Person; const x: PersonKeys = "age";

Slide 83

Slide 83 text

typeof + keyof const john = { name: "john", age: 24, }; !// "name" | "age" type Person = keyof typeof john; const variable: Person = "name"; console.log(variable);

Slide 84

Slide 84 text

interface IUser { id: number; name: string; } interface IAdmin extends IUser { token: string; } function getStuff(): unknown { const user: IUser = { id: 1, name: 'Jack' }; const admin: IAdmin = { id: 2, name: 'Hannah', token: '123' }; return Math.random() < 0.5 ? user : admin; } function isAdminUser(obj: unknown): obj is IAdmin { if (obj !!!== null !&& typeof obj !!=== 'object') { return 'token' in obj; } return false; } function main() { const user: unknown = getStuff(); if (isAdminUser(user)) { console.log(user.token); } } main(); If return value is true, obj is transformed to IAdmin

Slide 85

Slide 85 text

Satisfies interface ICustomImage { data: string; width: number; height: number; } type UserImage = string | ICustomImage; interface IUser { name: string; image: UserImage; } const user1: IUser = { name: 'Jack', image: 'url', }; console.log(user1.image.charAt(0)) const user2 = { name: 'Jack', image: 'url', } satisfies IUser; !// type is now { name: string; image: string } but it satiesfies IUser console.log(user2.image.charAt(0))

Slide 86

Slide 86 text

Promises with TS

Slide 87

Slide 87 text

Promise • Promises in TypeScript, as in JavaScript, are objects that represent the eventual completion (or failure) of an asynchronous operation, and its resulting value • Promise is in one of these states: • Pending • Fulfilled (resolve) • Rejected (reject) • TypeScript enhances working with Promises by allowing you to specify the type of value that a Promise resolves with

Slide 88

Slide 88 text

Example const myPromise: Promise = new Promise((resolve, reject) !=> { setTimeout(() !=> { resolve("Promise resolved after 2 seconds"); !// reject(new Error("Failed to resolve")); }, 2000); }); myPromise .then((result: string) !=> { console.log(result); !// TypeScript knows `result` is a string. }) .catch((error: Error) !=> { console.error(error.message); });

Slide 89

Slide 89 text

Example: async + await async function asyncFunction(): Promise { try { const result: string = await myPromise; console.log(result); } catch (error) { console.error(error); } }

Slide 90

Slide 90 text

VanillaJS and TS

Slide 91

Slide 91 text

Basics • Include TypeScript: Add TypeScript to your project, either by installing it via npm (npm install typescript) or by including it directly in your development environment. • Compile TypeScript: Write your TypeScript code (.ts files) and compile it to JavaScript (.js files) using the TypeScript compiler (tsc). This JavaScript code can then be included in your HTML files using tags. • HTML Integration: In your HTML, you reference the compiled JavaScript files. Your HTML and DOM manipulation code written in TypeScript will interact with the HTML elements after compilation to plain JavaScript.

Slide 92

Slide 92 text

DOM types, builtin with TS TypeScript DOM Type Description Example Usage HTMLElement Represents an HTML element. const element: HTMLElement = document.getElementById("myElement"); HTMLInputElement Represents an element. const input: HTMLInputElement = document.getElementById("myInput"); HTMLButtonElement Represents a element. const button: HTMLButtonElement = document.getElementById("myButton"); Event Represents an event that occurs in the DOM. element.addEventListener("click", (event: Event) => { ... }); MouseEvent Represents a mouse-related event (e.g., click, mouseover). element.addEventListener("click", (event: MouseEvent) => { ... }); KeyboardEvent Represents a keyboard-related event (e.g., keydown, keyup). input.addEventListener("keydown", (event: KeyboardEvent) => { ... }); NodeList Represents a collection of nodes (e.g., returned by querySelectorAll). const elements: NodeList = document.querySelectorAll(".myClass"); DocumentFragment Represents a minimal document object that has no parent. Useful for creating and manipulating a group of nodes. const fragment: DocumentFragment = document.createDocumentFragment(); Window Represents the global window object. const windowWidth: number = window.innerWidth;

Slide 93

Slide 93 text

Lab 02

Slide 94

Slide 94 text

Node, TS, Debugger

Slide 95

Slide 95 text

Node and TS • Configure .tsconfig.json to be Node friendly • Install types for Node development • @types/node

Slide 96

Slide 96 text

tsconfig.json • Set target to be ES2020 or newer • Use commonjs as module • Exclude node_modules • Will speed things up • Enable ES6 module import syntax for CommonJS • Enable strict mode for file names • In normal cases Windows would allow "from './userProfile'" and "from './UserProfile'. Let's prevent this so that latter is only allowed

Slide 97

Slide 97 text

"sourceMap": true • Instructs the TypeScript compiler to generate source map files alongside the compiled JavaScript files • These source map files create a bridge between the original TypeScript source code and the compiled JavaScript output • Enabling a more intuitive and effective debugging experience. • VS Code, Browser or Node.js debugger to reconstruct the original source and present the reconstructed original in the debugger • Essentially, source maps allow you to debug your TypeScript code as if you were running it natively

Slide 98

Slide 98 text

.tsconfig.json { "include": ["src/**/*"], "exclude": ["node_modules", "dist"], "compilerOptions": { "target": "ES2020", "module": "commonjs", !// import !.. -> require "moduleResolution": "node", !// will do proper module look_up in node_modules "outDir": "dist", "esModuleInterop": true, !// enables the use of default imports from CommonJS modules, !// making the import syntax cleaner and more !// consistent with ES6 modules. "forceConsistentCasingInFileNames": true, "strict": true, "sourceMap": true, "skipLibCheck": true } }

Slide 99

Slide 99 text

When compiling a .map file is created

Slide 100

Slide 100 text

VS Code Debugger • Create .vscode/launch.json file • Configure debugger and define to use the map files

Slide 101

Slide 101 text

launch.json file { "version": "0.2.0", "configurations": [ { "name": "Launch Program", "program": "${workspaceFolder}/src/index.ts", "request": "launch", "sourceMaps": true, "type": "node", "outFiles": ["${workspaceFolder}/dist/**/*.js"] } ] } Set this to true

Slide 102

Slide 102 text

We can now directly debug TS instead of compiled JS

Slide 103

Slide 103 text

Type Definitions • @types/node • Provides TypeScript definitions for Node.js built-in modules (like fs, http, path, etc.), enabling type checking and autocompletion for these APIs. • These definitions are not included with TypeScript and must be installed separately via npm • npm install !--save-dev @types/node • By adding type definitions for Node.js, you get immediate feedback on the usage of Node.js APIs directly in your editor, including parameter types, return types, and documentation

Slide 104

Slide 104 text

import { readFile } from "fs" readFile("src/index.ts", "utf-8", (error: NodeJS.ErrnoException | null, data: string | null) !=> { if (error) { console.error(`Error reading file`, error) } else { if (data) { console.log(data) } } })

Slide 105

Slide 105 text

import { readFile } from "fs" function readFileContent( filePath: string, callback: (error: NodeJS.ErrnoException | null, data: string | null) !=> void, ): void { readFile(filePath, "utf-8", (error, data) !=> { if (error) { console.error(`Error reading file ${filePath}:`, error) callback(error, null) } else { callback(null, data) } }) } readFileContent("src/index.ts", (error: NodeJS.ErrnoException | null, data: string | null) !=> { if (error) { console.log(error) } else { if (data) { console.log(data) } } })

Slide 106

Slide 106 text

Third-party libraries • If you're using a library in your Node.js project, you can often find and install these definitions from npm registry • Example: • npm install !--save-dev @types/express

Slide 107

Slide 107 text

Import: Use ES6 Module System Syntax !// Nodejs module import path from 'path'; !// My Own Module import parseCSVData from './util'; !// util.ts export default function parseCSVData(csvData: string): User[] { !.. }

Slide 108

Slide 108 text

Express and TS

Slide 109

Slide 109 text

Overview • Install dependencies • npm i express @types/express • Again by using TS • Static Typing • Improved Code Quality • Robust tooling • Decorators

Slide 110

Slide 110 text

Static Typing import express, { Request, Response } from 'express'; const app = express(); app.use(express.json()); app.post('/users', (req: Request, res: Response) !=> { res.json({ message: `User ${req.body.username} created` }); }); app.listen(3000, () !=> console.log('Server running on port 3000')); Interface Interface any

Slide 111

Slide 111 text

Improvement Static Typing import express, { Request, Response } from 'express'; const app = express(); app.use(express.json()); interface IBody { username: string; email: string; } interface IRequest extends Request { body: IBody; } app.post('/users', (req: IRequest, res: Response) !=> { res.json({ message: `User ${req.body.username} created` }); }); app.listen(3000, () !=> console.log('Server running on port 3000')); username is now string

Slide 112

Slide 112 text

Readability? import express, { Request, Response } from 'express'; const app = express(); app.use(express.json()); interface IUser { username: string; email: string; } !// eslint-disable-next-line no-unused-vars class User implements IUser { constructor( public username: string, public email: string, ) {} } app.get('/users/:username', (req: Request, res: Response) !=> { const user = new User(req.params.username, '[email protected]'); res.json(user); }); app.listen(3000, () !=> console.log('Server running on port 3000'));

Slide 113

Slide 113 text

Decorators import express, { Request, Response } from 'express'; const app = express(); app.use(express.json()); !// "experimentalDecorators": true function route(path: string) { return function ( target: unknown, propertyKey: string, descriptor: PropertyDescriptor, ) { app.get(path, (req: Request, res: Response) !=> { const method = descriptor.value; method(req, res); }); }; } class UserController { @route('/user') getUser(req: Request, res: Response) { res.json({ username: 'JohnDoe' }); } } app.listen(3000, () !=> console.log('Server running on port 3000'));

Slide 114

Slide 114 text

Lab 03 + 04

Slide 115

Slide 115 text

Advanced Types

Slide 116

Slide 116 text

Advanced Types • Union types • let x : string | number = "hello" • Type alias • type StringOrNumber = string | number • let x : StringOrNumber = 5 • Intersection types • let x : A & B = foo • Literal types • let direction: "up" | "down" | "left" | "right" = "up" • Type aliases • type UserID = string | number; • let x : UserID = "foo" • Interfaces • Generics

Slide 117

Slide 117 text

Union Types let id: string | number; id = "1234"; !// Valid id = 5678; !// Valid

Slide 118

Slide 118 text

Type Alias type StringOrNumber = string | number let x : StringOrNumber = "hello"

Slide 119

Slide 119 text

Literal Types let direction: "up" | "down" | "left" | "right"; direction = "up"; !// Valid direction = "down"; !// Valid

Slide 120

Slide 120 text

Type Alias type UserID = string | number; let userId: UserID; userId = "123"; !// Valid userId = 456; !// Valid type Response = "success" | "failure"; type HTTPResponse = { code: 200 | 400 | 500; status: Response; }; const response: HTTPResponse = { code: 200, status: "success", };

Slide 121

Slide 121 text

Problem? https://www.youtube.com/watch?v=xsfdypZCLQ8 type LocationState = { state: "Loading" | "Success" | "Error" coords!?: { lat: number, lon: number } error!?: { message: string } } function printLocation(location: LocationState) { switch (location.state) { case "Loading": console.log(location.state) break case "Success": console.log(location.coords.lat, location.coords.lon) break case "Error": console.log(location.error.message) break } } printLocation({ state: "Success" })

Slide 122

Slide 122 text

Better? type LoadingLocationState = { state: "Loading" } type SuccessLocationState = { state: "Success" coords: { lat: number, lon: number } } type ErrorLocationState = { state: "Error" error: { message: "error" } } type LocationState = LoadingLocationState | SuccessLocationState | ErrorLocationState

Slide 123

Slide 123 text

Intersection Types type Employee = { name: string; startDate: Date; }; type Role = { roleName: string; }; type EmployeeRole = Employee & Role; const employeeRole: EmployeeRole = { name: "John Doe", startDate: new Date(), roleName: "Developer" }; Now EmployeeRole has name, startDate and roleName

Slide 124

Slide 124 text

Interfaces

Slide 125

Slide 125 text

Basic Interface interface User { id: number; name: string; } const user: User = { id: 1, name: "John Doe", };

Slide 126

Slide 126 text

Interface with Optional Properties interface Product { id: number; name: string; price!?: number; !// Optional property } const book: Product = { id: 1, name: "The Great Gatsby", };

Slide 127

Slide 127 text

Interface with readonly properties interface Point { readonly x: number; readonly y: number; } let point: Point = { x: 10, y: 20 }; !// point.x = 5; !// Error:

Slide 128

Slide 128 text

Interface for Function Types interface TransformCallback { (element: number): number; } function processData(data: number[], callback: TransformCallback): number[] { return data.map(callback); } const numbers = [1, 2, 3, 4, 5]; const modify: TransformCallback = (element: number) !=> element * 2; const doubledNumbers = processData(numbers, modify); console.log(doubledNumbers); !// Outputs: [2, 4, 6, 8, 10] Will only take certain kind of function as an argument

Slide 129

Slide 129 text

Extending Interfaces interface Shape { color: string; } interface Square extends Shape { sideLength: number; } let square: Square = { color: "blue", sideLength: 10, };

Slide 130

Slide 130 text

Implementing Interfaces interface ClockInterface { currentTime: Date; setTime(d: Date): void; } class Clock implements ClockInterface { currentTime: Date = new Date(); setTime(d: Date) { this.currentTime = d; } }

Slide 131

Slide 131 text

Type Alias vs Interface • Type aliases and interfaces are very similar, and in many cases you can choose between them freely • Almost all features of an interface are available in type • Differences • Declaration Merging: Interfaces support declaration merging, type aliases do not. • Extending and Implementing: Interfaces use extends and can be implemented by classes, while type aliases use intersections (&) for extension. • Usage: Interfaces are primarily used for defining object shapes, whereas type aliases are more flexible and can define various types including primitives, unions, and tuples.

Slide 132

Slide 132 text

Examples interface Animal { name: string; } interface Bear extends Animal { honey: boolean; } const bear = getBear(); bear.name; bear.honey; type Animal = { name: string; } type Bear = Animal & { honey: boolean; } const bear = getBear(); bear.name; bear.honey;

Slide 133

Slide 133 text

Interface: Declaration Merging interface Person { name: string; } interface Person { age: number; } !// This is equivalent to: interface Person { name: string; age: number; }

Slide 134

Slide 134 text

Interface: Classes interface Person { name: string; age: number; } class Employee implements Person { name: string; age: number; position: string; constructor(name: string, age: number, position: string) { this.name = name; this.age = age; this.position = position; } }

Slide 135

Slide 135 text

When to use • Interface • When you need to use declaration merging. • When you need a type to be implemented by a class. • Type alias • When you need to define a union, tuple, or more complex type. • When you do not need to merge type declarations.

Slide 136

Slide 136 text

Generics

Slide 137

Slide 137 text

About Generics • Powerful feature that allows you to create reusable, flexible, and type-safe components • You can write more general, abstract code without losing the type safety and IntelliSense provided by TypeScript's static typing. • Why? • Type Safety • Resuable code • Better Tooling

Slide 138

Slide 138 text

Generics function identity(arg: T): T { return arg; } let output1 = identity("myString"); let output2 = identity(100);

Slide 139

Slide 139 text

Generics with classess const tigerCage = new Cage(); tigerCage.addOccupant(new Tiger("Sher Khan")); tigerCage.listOccupants(); !// Output: Sher Khan roars. const elephantCage = new Cage(); elephantCage.addOccupant(new Elephant("Dumbo")); elephantCage.listOccupants(); !// Output: Dumbo trumpets.

Slide 140

Slide 140 text

Generics with classess class Cage { occupants: T[] = []; addOccupant(occupant: T): void { this.occupants.push(occupant); } listOccupants(): void { this.occupants.forEach(occupant !=> { console.log(occupant.speak()); }); } }

Slide 141

Slide 141 text

Animal abstract class Animal { name: string; constructor(name: string) { this.name = name; } !// Declare an abstract method. This forces subclasses to implement it. abstract speak(): string; }

Slide 142

Slide 142 text

Tiger and Elephant class Tiger extends Animal { speak(): string { return `${this.name} roars.`; } } class Elephant extends Animal { speak(): string { return `${this.name} trumpets.`; } }

Slide 143

Slide 143 text

Default Value interface Container { contents: T; } const stringContainer: Container = { contents: "Hello" }; const numberContainer: Container = { contents: 5 }; console.log(stringContainer.contents); console.log(numberContainer.contents);

Slide 144

Slide 144 text

Utility Types Transforming, Filtering, Misc

Slide 145

Slide 145 text

Utility types: DRY • A utility type in TypeScript is a generic type that provides a convenient way to transform one type into another • Transformations can include making • properties optional • read-only • picking a subset of properties

Slide 146

Slide 146 text

Transforming Types • Partial: • Creates a type with all properties of the provided type set to optional. • Required: • Creates a type with all properties of the provided type set to required. • Readonly: • Creates a type with all properties of the provided type set to read-only. • Pick: • Creates a type by picking only the specified keys from the provided type. • Omit: • Creates a type by omitting the specified keys from the provided type.

Slide 147

Slide 147 text

interface Person = { name: string; age: number; }; type ReadonlyPerson = Readonly; const person: ReadonlyPerson = { name: "Jane Doe", age: 32, }; console.log(person); !// Trying to modify the properties will result in a TypeScript error. person.name = "John Doe"; !// Error: Cannot assign to 'name' because it is a read-only property. person.age = 33; !// Error: Cannot assign to 'age' because it is a read-only property. Creating a new type from an existing one

Slide 148

Slide 148 text

function updatePerson(person: Person, updates: Partial): Person { return { !!...person, !!...updates }; } const person: Person = { name: "Jane Doe", age: 32, }; console.log("Before update:", person); !// Updating the person's information partially const updatedPerson = updatePerson(person, { name: "John Doe" }); console.log("After update:", updatedPerson); We can pass an object with only subset of the interface's properties Makes all properties optional

Slide 149

Slide 149 text

interface User { id: number; name: string; email: string; age: number; } type UserProfile = Pick; let jack : UserProfile; jack.name = "jack" jack.email = "[email protected]" Pick only name and email

Slide 150

Slide 150 text

interface User { id: number; name: string; email: string; age: number; } type UserWithoutIdAndAge = Omit; !// Now UserWithoutIdAndAge will have only 'name' and 'email' properties const user: UserWithoutIdAndAge = { name: 'Alice', email: '[email protected]' }; console.log(user); !// Output: { name: 'Alice', email: '[email protected]' } Exclude id and age

Slide 151

Slide 151 text

interface User { id!?: number; name!?: string; email!?: string; } type RequiredUser = Required; !// Now RequiredUser will have all properties of User type as required const user: RequiredUser = { id: 1, name: 'Alice', email: '[email protected]' }; console.log(user); !// Output: { id: 1, name: 'Alice', email: '[email protected]' } Make all properties required

Slide 152

Slide 152 text

Filtering Types • Exclude: • Creates a type by excluding from Type all properties that are assignable to ExcludedUnion. • Extract: • Creates a type by extracting from Type all properties that are assignable to Union. • NonNullable: • Creates a type by excluding null and undefined from Type.

Slide 153

Slide 153 text

Exclude: Remove Stuff type T1 = string | number | boolean; type T2 = Exclude; !// T2 is 'string' const a: T2 = "hello"; console.log(a);

Slide 154

Slide 154 text

Extract: Keep Stuff type T3 = string | number | boolean; type T4 = Extract; !// T4 is 'number | boolean' const a: T4 = 5; console.log(a);

Slide 155

Slide 155 text

NonNullable: Get rid of null and undefined type T5 = string | number | undefined | null; type T6 = NonNullable; !// T6 is 'string | number' const a: T6 = 5; console.log(a);

Slide 156

Slide 156 text

Misc Type: ReturnType const func = () !=> { return "hello"; }; type ReturnT = ReturnType; const x: ReturnT = "hello world"; console.log(x); Get the return type of the function!

Slide 157

Slide 157 text

Misc Type: ReturnType with promises const func = async () !=> { return "hello"; }; type ReturnT = Awaited>; string

Slide 158

Slide 158 text

Misc Type: Conditional Type • Type chosen can depend on a condition • T extends U ? X : Y • Example type IsString = T extends string ? boolean : number; !// Using the conditional type type TestString = IsString; !// Resolves to boolean type TestNumber = IsString; !// Resolves to number type YesOrNo = 42 extends number ? "Yes" : "No";

Slide 159

Slide 159 text

Misc Type: Indexed Access Type • We can use an indexed access type to look up a specific property on another type • Example type Person = { age: number; name: string; alive: boolean }; type Age = Person["age"]; const x : Age = 80

Slide 160

Slide 160 text

Lab 05

Slide 161

Slide 161 text

Classes

Slide 162

Slide 162 text

Classes • In addition to ES.next features • Type annotations • Access modifiers (public, private, protected) vs # in ES.next • Abstract classes • Interfaces • Decorators

Slide 163

Slide 163 text

class Person { constructor(public name: string, protected id: number, private age : number) { } } class Student extends Person { constructor() { super("jack", 1, 50) this.id = 2 !//this.age = 80 } } let jack = new Person("Jack", 1, 40) console.log(jack.name) !// console.log(jack.id) !// console.log(jack.age) Will create also attributes

Slide 164

Slide 164 text

interface Borrowable { isAvailable: boolean; borrow(): void; returnItem(): void; } abstract class Item { protected title: string; public isAvailable: boolean; constructor(title: string) { this.title = title; this.isAvailable = true; } abstract describe(): void; } Can contain variables and methods You cannot create instance This must be implemented

Slide 165

Slide 165 text

class Book extends Item implements Borrowable { private author: string; constructor(title: string, author: string) { super(title); this.author = author; } describe(): void { console.log(`Book: ${this.title} by ${this.author}`); } borrow(): void { if (this.isAvailable) { this.isAvailable = false; console.log(`Borrowed book: ${this.title}`); } else { console.log(`Book is not available: ${this.title}`); } } returnItem(): void { this.isAvailable = true; console.log(`Returned book: ${this.title}`); } } abstract class Interface Interface

Slide 166

Slide 166 text

class DVD extends Item implements Borrowable { private runtimeInMinutes: number; constructor(title: string, runtimeInMinutes: number) { super(title); this.runtimeInMinutes = runtimeInMinutes; } describe(): void { console.log( `DVD: ${this.title}, Runtime: ${this.runtimeInMinutes} minutes`, ); } borrow(): void { if (this.isAvailable) { this.isAvailable = false; console.log(`Borrowed DVD: ${this.title}`); } else { console.log(`DVD is not available: ${this.title}`); } } returnItem(): void { this.isAvailable = true; console.log(`Returned DVD: ${this.title}`); } }

Slide 167

Slide 167 text

const myBook = new Book("The Hobbit", "J.R.R. Tolkien"); myBook.describe(); myBook.borrow(); myBook.returnItem(); const myDVD = new DVD("Inception", 148); myDVD.describe(); myDVD.borrow(); myDVD.returnItem();

Slide 168

Slide 168 text

Getters and Setters class Person { private _age: number; constructor(age: number) { this._age = age; } get age(): number { return this._age; } !// Setter for the age property set age(value: number) { if (value < 0 !|| value > 150) { throw new TypeError("Invalid age."); } else { this._age = value; } } } const person = new Person(30); !// Using the getter to access the age console.log(person.age); !// Output: 30 !// Using the setter to update the age person.age = -35;

Slide 169

Slide 169 text

Decorators

Slide 170

Slide 170 text

Decorators • Decorators are a special kind of declaration in TypeScript that can be attached to a class declaration, method, accessor, property, or parameter • Reusable code for existing one • No functions! • In JS it is in proposal stage • Like annotations in Java • Decorators use the form @expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration. • Popular in Angular • We will use decorators more than we create them • You must enable it • "experimentalDecorators": true

Slide 171

Slide 171 text

Hello World function HelloWorld(target: Function) { console.log("Hello World"); } @HelloWorld class MyExampleClass { constructor() { console.log("MyExampleClass instance created"); } } const myExample = new MyExampleClass(); Passes the constructor function of MyClass

Slide 172

Slide 172 text

Hello World: Factory Pattern function HelloWorldFactory() { return function(target: Function) { console.log("Hello World"); }; } @HelloWorldFactory() class MyExampleClass { constructor() { console.log("MyExampleClass instance created"); } } const myExample = new MyExampleClass(); Invokes the inner function

Slide 173

Slide 173 text

Hello World: Factory Pattern function HelloWorldFactory(message: string) { return function(target: Function) { console.log(message); }; } @HelloWorldFactory("Custom Hello World Message") class MyExampleClass { constructor() { console.log("MyExampleClass instance created"); } } const myExample = new MyExampleClass(); We can now pass arguments

Slide 174

Slide 174 text

Hello World: Factory Pattern function HelloWorldFactory(message: string) { return function(target: Function) { target.prototype.helloWorld = function() { console.log(message); }; }; } @HelloWorldFactory("Custom Hello World Message") class MyExampleClass { constructor() { console.log("MyExampleClass instance created"); } } const myExample = new MyExampleClass(); (myExample as any).helloWorld(); TS typesystem can't find this, so we will have to do ugly things.. Constructor function, but this is rather vague, can be any function...

Slide 175

Slide 175 text

Spread operator function concatenateStrings(!!...strings: string[]): string { return strings.join(' '); } const result = concatenateStrings('Hello', 'world!', 'How', 'are', 'you?'); console.log(result);

Slide 176

Slide 176 text

Constructor function type class ExampleClass { constructor(message: string) {} } const constructor1: Function = ExampleClass const constructor2: new (m: string) !=> ExampleClass = ExampleClass It is constructor function, must be invoked with new Dual purpose, both type and constructor

Slide 177

Slide 177 text

Constructor function type class ExampleClass1 { constructor(message: string) {} } class ExampleClass2 { constructor(value: number) {} } var constructor: new (!!...args: any[]) !=> any constructor = ExampleClass1 constructor = ExampleClass2 Like any constructor

Slide 178

Slide 178 text

Hello World: Factory Pattern function HelloWorldFactory(message: string) { return function(target: new (!!...args: any[]) !=> any) { target.prototype.helloWorld = function() { console.log(message); }; }; } @HelloWorldFactory("Custom Hello World Message") class MyExampleClass { constructor() { console.log("MyExampleClass instance created"); } } const myExample = new MyExampleClass(); (myExample as any).helloWorld(); Like any constructor

Slide 179

Slide 179 text

Hello World: Factory Pattern function HelloWorldMethodDecorator(message: string) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { !// prototype of the class console.log(target) !// "helloWorld" (name of the method) console.log(propertyKey) !// Descriptor of the property console.log(descriptor) }; } class MyExampleClass { @HelloWorldMethodDecorator("Custom Hello World Message") helloWorld() { console.log("Executing helloWorld method"); } } const myExample = new MyExampleClass(); myExample.helloWorld();

Slide 180

Slide 180 text

Removal of Factory function Modify(target: unknown, propertyKey: string, descriptor: PropertyDescriptor) { const original = descriptor.value descriptor.value = function () { console.log("A") original() console.log("B") } }; class MyExampleClass { @Modify helloWorld() { console.log("Executing helloWorld method"); } } const myExample = new MyExampleClass(); myExample.helloWorld();

Slide 181

Slide 181 text

Removal of Factory function Modify(target: unknown, propertyKey: string, descriptor: PropertyDescriptor) { const original = descriptor.value descriptor.value = function (msg: string) { console.log("A") original.call(this, msg) console.log("C") } }; class MyExampleClass { @Modify helloWorld(msg: string) { console.log(msg); } } const myExample = new MyExampleClass(); myExample.helloWorld("B");

Slide 182

Slide 182 text

Removal of Factory function Modify(target: unknown, propertyKey: string, descriptor: PropertyDescriptor) { const original = descriptor.value descriptor.value = function () { console.log("A") original.apply(this, arguments) console.log("C") } }; class MyExampleClass { @Modify helloWorld(msg: string) { console.log(msg); } } const myExample = new MyExampleClass(); myExample.helloWorld("B");

Slide 183

Slide 183 text

Lab 06-07

Slide 184

Slide 184 text

React and TS

Slide 185

Slide 185 text

Benefts • Strong Typing • Autocompletion and IntelliSense • Early Detection of Errors • Better Maintainability

Slide 186

Slide 186 text

Aspect Description Type Safety Utilize static typing to catch errors during development and improve code maintainability. JSX and TSX Write JSX (JavaScript XML) with TypeScript and annotate JSX elements and props with types. Functional Components and Hooks Understand how to define functional components and annotate their props and state with TypeScript types. Component Lifecycle Methods If using class components, annotate component lifecycle methods with appropriate types. Handling Events Annotate event handlers with correct event types (e.g., MouseEvent, KeyboardEvent) to ensure type safety. State Management Libraries Define types for actions, reducers, and store states in state management libraries like Redux or MobX. Higher-Order Components (HOCs) Define types for higher-order components and render props to maintain type safety. Type Definitions for Libraries Install TypeScript type definitions for third-party React libraries to enable type checking with these libraries. Type Inference Leverage TypeScript's type inference capabilities to reduce the need for explicit type annotations while maintaining type safety. React Context and Refs Define types for React context objects and refs to ensure type safety when using these features in your application.

Slide 187

Slide 187 text

Type Description ReactNode Represents a renderable JSX element, including elements, strings, or fragments. ReactElement Represents a single JSX element. It's a generic type with props and type parameters. React.FC Type alias for defining functional components with props. For example, React.FC. React.ComponentType Represents any component type, including class components, functional components, or HOCs. ReactProps Generic type defining the shape of props for a component. For example, ReactProps. ReactState Generic type defining the shape of state for a component. For example, ReactState. ReactRef Generic type defining the type of a reference to a DOM element or a React component. For example, ReactRef. ReactChildren Provides utilities for working with children elements, such as React.Children.map and React.Children.toArray. ReactEventHandler Generic type defining an event handler function for handling DOM events. For example, ReactEventHandler. ReactEvent Generic type defining the type of a synthetic event in React. For example, ReactEvent.

Slide 188

Slide 188 text

React.FC • React.FC is a TypeScript generic type (where FC stands for Function Component) used in React development to type functional components, with T representing the shape of the component's props.

Slide 189

Slide 189 text

Simple Example interface MyComponentProps { message: string; } const MyComponent: React.FC = (props) !=> { return
{props.message}!
; };

Slide 190

Slide 190 text

Simple Example interface MyComponentProps { message: string; } const MyComponent = ({ message }: MyComponentProps) !=> { return
{message}!
; };

Slide 191

Slide 191 text

MouseEvent const ClickButton: React.FC = () !=> { const handleClick = (event: React.MouseEvent) !=> { console.log("Button clicked!", event.currentTarget); }; return Click Me!; };

Slide 192

Slide 192 text

ChangeEvent const InputField: React.FC = () !=> { const [value, setValue] = React.useState(""); const handleChange = (event: React.ChangeEvent) !=> { setValue(event.target.value); }; return ; };

Slide 193

Slide 193 text

interface UserData { id: number; name: string; } function isValidUserData(obj: unknown): obj is UserData { !// Ensure obj is an object and not null if (typeof obj !!=== 'object' !&& obj !!!== null) { !// Check if the 'id' property exists and is of type 'number' if ('id' in obj !&& typeof obj.id !!=== 'number') { !// Check if the 'name' property exists and is of type 'string' if ('name' in obj !&& typeof obj.name !!=== 'string') { return true; } } } return false; } const UserComponent: React.FC = () !=> { const [user, setUser] = useState(null); useEffect(() !=> { const fetchData = async () !=> { const response = await fetch('https:!//jsonplaceholder.typicode.com/users/1'); const userData: unknown = await response.json(); if (isValidUserData(userData)) { setUser(userData); } else { console.log("Invalid user data:", userData); } }; fetchData(); }, []); !// Empty dependency array means this runs once on mount if (!user) return
Loading!!...!
; return (

User ID: {user.id}!

Name: {user.name}!

!
); };

Slide 194

Slide 194 text

interface UserData { id: number; name: string; } function isValidUserData(obj: unknown): obj is UserData { return ( typeof obj !!=== 'object' !&& obj !!!== null !&& 'id' in obj !&& typeof obj.id !!=== 'number' !&& 'name' in obj !&& typeof obj.name !!=== 'string' ); } const UserComponent: React.FC = () !=> { const [user, setUser] = useState(null); useEffect(() !=> { const fetchData = async () !=> { const response = await fetch('https:!//jsonplaceholder.typicode.com/users/1'); const userData: unknown = await response.json(); if (isValidUserData(userData)) { setUser(userData); } else { console.log("Invalid user data:", userData); } }; fetchData(); }, []); !// Empty dependency array means this runs once on mount if (!user) return
Loading!!...!
; return (

User ID: {user.id}!

Name: {user.name}!

!
); };

Slide 195

Slide 195 text

Lab 08