Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TypeScript for JS Developers

TypeScript for JS Developers

Jussi Pohjolainen

June 04, 2024
Tweet

More Decks by Jussi Pohjolainen

Other Decks in Technology

Transcript

  1. What if... • 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
  2. 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
  3. Overview • Superset of JavaScript: • TypeScript extends JavaScript by

    adding additional syntax and features. Any valid JavaScript code is also valid TypeScript code, which means 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
  4. Advantages to JS • Static Typing • Lot's of additional

    types! • Advanced OO features (more like java) • Tooling • 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
  5. 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
  6. 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.
  7. NPM Project • NPM project for basis • npm init

    -y • ESlint for linting • npm install --save-dev [email protected] • Prettier for styling • npm install -–save-dev prettier • TypeScript • npm install --save-dev typescript • npx tsc --init
  8. 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
  9. 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..
  10. 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
  11. 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
  12. 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
  13. 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'); }
  14. 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);
  15. ESLint • Quality tool • Customizable Rules • Plugin System

    • eslint-plugin-react, eslint-plugin-vue, @typescript-eslint/eslint-plugin • Automatic Code Fixing • Integration with Development Environments
  16. ESLint + TS • At this time (2024-06-06) ESLint has

    problems with TS plugins • Use ESLint 8.57 for compatibility • Install • npm install --save-dev [email protected]
  17. Plugins • @typescript-eslint/parser • Replace default JS parser with TS

    parser. Parser reads TS code and produces abstract syntax tree which ESlint will check • @typescript-eslint/eslint-plugin • Set of ESlint rules designed for TS • Type system and syntax
  18. .eslintrc.js "rules": { "no-var": "error", eqeqeq: "error", "no-unused-vars": "warn", "prefer-const":

    "error", "no-else-return": "error", curly: "error", "no-multiple-empty-lines": ["error", { max: 1 }], camelcase: "error", "no-eval": "error", } You can add different rules for ESLint
  19. Gives linting errors Tries to fix linting errors Got one

    linting error fixed, styling problems exists
  20. 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 eslint-config-prettier eslint-plugin-prettier
  21. 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.
  22. Prettier modification: .prettierrc { "semi": false, "singleQuote": true, "printWidth": 100,

    "tabWidth": 4, "useTabs": false, "trailingComma": "all", "bracketSpacing": true, "jsxBracketSameLine": false, "arrowParens": "avoid", "endOfLine": "lf" }
  23. 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
  24. npx tsc • Compile TS to JS using • npx

    tsc • By default it will compile to ES2016 • You can change the target
  25. npx tsc TypeScript const PI: number = 3.14; console.log(PI); ES6

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

    "use strict"; var PI = 3.14; console.log(PI);
  27. ECMAScript Types Primitive types • boolean • null • undefined

    • number • bigint • string • symbol Objects • Arrays • Functions • Dates, RegExp… • Custom objects
  28. 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");
  29. 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
  30. 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";
  31. 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' }
  32. 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
  33. 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
  34. 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"
  35. 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.
  36. 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 }
  37. Everyday TypeScript types Primitive • any • unknown • never

    • void Objects • Enums • Tuples • Arrays • Function types
  38. 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
  39. Unknown usage // Function to fetch a Chuck Norris joke

    async function fetchChuckNorrisJoke(): Promise<string> { 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<any>
  40. 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<string> { 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
  41. 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();
  42. 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;
  43. 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
  44. enum enum ResponseStatus { Success = "SUCCESS", Failure = "FAILURE",

    Pending = "PENDING" } let response: ResponseStatus = ResponseStatus.Success; console.log(response); // Outputs: "SUCCESS"
  45. 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"
  46. 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
  47. 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<number>. 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.
  48. 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"
  49. 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
  50. 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]]
  51. 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"]
  52. array, using generic type let ages: Array<number> = [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]
  53. 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]]
  54. 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
  55. 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
  56. 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
  57. 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(); } }
  58. 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.
  59. 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}!`;
  60. 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);
  61. keyof type Person = { name: string; age: number; };

    // "name" | "age" type PersonKeys = keyof Person; const x: PersonKeys = "age";
  62. typeof + keyof const john = { name: "john", age:

    24, }; // "name" | "age" type Person = keyof typeof john; const variable: Person = "name"; console.log(variable);
  63. 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
  64. 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))
  65. 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
  66. Example const myPromise: Promise<string> = 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); });
  67. Example: async + await async function asyncFunction(): Promise<void> { try

    { const result: string = await myPromise; console.log(result); } catch (error) { console.error(error); } }
  68. 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 <script> 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.
  69. 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 <input> element. const input: HTMLInputElement = document.getElementById("myInput"); HTMLButtonElement Represents a <button> 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;
  70. Node and TS • Configure .tsconfig.json to be Node friendly

    • Install types for Node development • @types/node
  71. 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
  72. "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
  73. .tsconfig.json { "include": ["src/**/*"], "exclude": ["node_modules", "dist"], "compilerOptions": { "target":

    "ES2020", "module": "commonjs", "moduleResolution": "node", "outDir": "dist", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "sourceMap": true, "rootDir": "./src", "skipLibCheck": true } }
  74. 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
  75. 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
  76. 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
  77. 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[] { .. }
  78. Overview • Install dependencies • npm i express @types/express •

    Again by using TS • Static Typing • Improved Code Quality • Robust tooling • Decorators
  79. 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
  80. 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
  81. 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'));
  82. 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'));
  83. 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
  84. Literal Types let direction: "up" | "down" | "left" |

    "right"; direction = "up"; // Valid direction = "down"; // Valid
  85. 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", };
  86. 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" })
  87. 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
  88. 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
  89. Basic Interface interface User { id: number; name: string; }

    const user: User = { id: 1, name: "John Doe", };
  90. Interface with Optional Properties interface Product { id: number; name:

    string; price?: number; // Optional property } const book: Product = { id: 1, name: "The Great Gatsby", };
  91. Interface with readonly properties interface Point { readonly x: number;

    readonly y: number; } let point: Point = { x: 10, y: 20 }; // point.x = 5; // Error:
  92. 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
  93. Extending Interfaces interface Shape { color: string; } interface Square

    extends Shape { sideLength: number; } let square: Square = { color: "blue", sideLength: 10, };
  94. Implementing Interfaces interface ClockInterface { currentTime: Date; setTime(d: Date): void;

    } class Clock implements ClockInterface { currentTime: Date = new Date(); setTime(d: Date) { this.currentTime = d; } }
  95. 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.
  96. 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;
  97. Interface: Declaration Merging interface Person { name: string; } interface

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

    Person { age: number; } // This is equivalent to: interface Person { name: string; age: number; }
  99. 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; } }
  100. 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.
  101. 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
  102. Generics function identity<T>(arg: T): T { return arg; } let

    output1 = identity<string>("myString"); let output2 = identity<number>(100);
  103. Generics with classess const tigerCage = new Cage<Tiger>(); tigerCage.addOccupant(new Tiger("Sher

    Khan")); tigerCage.listOccupants(); // Output: Sher Khan roars. const elephantCage = new Cage<Elephant>(); elephantCage.addOccupant(new Elephant("Dumbo")); elephantCage.listOccupants(); // Output: Dumbo trumpets.
  104. Generics with classess class Cage<T extends Animal> { occupants: T[]

    = []; addOccupant(occupant: T): void { this.occupants.push(occupant); } listOccupants(): void { this.occupants.forEach(occupant => { console.log(occupant.speak()); }); } }
  105. 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; }
  106. Tiger and Elephant class Tiger extends Animal { speak(): string

    { return `${this.name} roars.`; } } class Elephant extends Animal { speak(): string { return `${this.name} trumpets.`; } }
  107. Default Value interface Container<T = string> { contents: T; }

    const stringContainer: Container = { contents: "Hello" }; const numberContainer: Container<number> = { contents: 5 }; console.log(stringContainer.contents); console.log(numberContainer.contents);
  108. 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
  109. Transforming Types • Partial<Type>: • Creates a type with all

    properties of the provided type set to optional. • Required<Type>: • Creates a type with all properties of the provided type set to required. • Readonly<Type>: • Creates a type with all properties of the provided type set to read-only. • Pick<Type, Keys>: • Creates a type by picking only the specified keys from the provided type. • Omit<Type, Keys>: • Creates a type by omitting the specified keys from the provided type.
  110. interface Person = { name: string; age: number; }; type

    ReadonlyPerson = Readonly<Person>; 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
  111. function updatePerson(person: Person, updates: Partial<Person>): 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
  112. interface User { id: number; name: string; email: string; age:

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

    number; } type UserWithoutIdAndAge = Omit<User, 'id' | 'age'>; // 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
  114. interface User { id?: number; name?: string; email?: string; }

    type RequiredUser = Required<User>; // 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
  115. Filtering Types • Exclude<Type, ExcludedUnion>: • Creates a type by

    excluding from Type all properties that are assignable to ExcludedUnion. • Extract<Type, Union>: • Creates a type by extracting from Type all properties that are assignable to Union. • NonNullable<Type>: • Creates a type by excluding null and undefined from Type.
  116. Exclude: Remove Stuff type T1 = string | number |

    boolean; type T2 = Exclude<T1, number | boolean>; // T2 is 'string' const a: T2 = "hello"; console.log(a);
  117. Extract: Keep Stuff type T3 = string | number |

    boolean; type T4 = Extract<T3, number | boolean>; // T4 is 'number | boolean' const a: T4 = 5; console.log(a);
  118. NonNullable: Get rid of null and undefined type T5 =

    string | number | undefined | null; type T6 = NonNullable<T5>; // T6 is 'string | number' const a: T6 = 5; console.log(a);
  119. Misc Type: ReturnType const func = () => { return

    "hello"; }; type ReturnT = ReturnType<typeof func>; const x: ReturnT = "hello world"; console.log(x); Get the return type of the function!
  120. Misc Type: ReturnType with promises const func = async ()

    => { return "hello"; }; type ReturnT = Awaited<ReturnType<typeof func>>; string
  121. Misc Type: Conditional Type • Type chosen can depend on

    a condition • T extends U ? X : Y • Example type IsString<T> = T extends string ? boolean : number; // Using the conditional type type TestString = IsString<string>; // Resolves to boolean type TestNumber = IsString<number>; // Resolves to number type IsNumber = 42 extends number ? "Yes" : "No";
  122. 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
  123. Classes • In addition to ES.next features • Type annotations

    • Access modifiers (public, private, protected) vs # in ES.next • Abstract classes • Interfaces • Decorators
  124. 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
  125. 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
  126. 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
  127. 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}`); } }
  128. 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();
  129. 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;
  130. 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
  131. 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
  132. 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
  133. 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
  134. 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...
  135. Spread operator function concatenateStrings(...strings: string[]): string { return strings.join(' ');

    } const result = concatenateStrings('Hello', 'world!', 'How', 'are', 'you?'); console.log(result);
  136. 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
  137. 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
  138. 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
  139. 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();
  140. 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();
  141. 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");
  142. 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");
  143. Benefts • Strong Typing • Autocompletion and IntelliSense • Early

    Detection of Errors • Better Maintainability
  144. 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.
  145. 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<Props>. 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<Props>. ReactState Generic type defining the shape of state for a component. For example, ReactState<State>. ReactRef Generic type defining the type of a reference to a DOM element or a React component. For example, ReactRef<HTMLDivElement>. 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<MouseEvent>. ReactEvent Generic type defining the type of a synthetic event in React. For example, ReactEvent<MouseEvent>.
  146. React.FC<T> • React.FC<T> 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.
  147. Simple Example interface MyComponentProps { message: string; } const MyComponent:

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

    = ({ message }: MyComponentProps) => { return <div>{message}</div>; };
  149. MouseEvent const ClickButton: React.FC = () => { const handleClick

    = (event: React.MouseEvent<HTMLButtonElement>) => { console.log("Button clicked!", event.currentTarget); }; return <button onClick={handleClick}>Click Me</button>; };
  150. ChangeEvent const InputField: React.FC = () => { const [value,

    setValue] = React.useState(""); const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { setValue(event.target.value); }; return <input type="text" value={value} onChange={handleChange} />; };
  151. interface UserData { id: number; name: string; } function isValidUserData(obj:

    unknown): obj is UserData { // First, ensure obj is an object and not null if (typeof obj === 'object' && obj !== null) { // Now, we know obj is an object type, so we can check its properties const user = obj as { id: unknown, name: unknown }; return typeof user.id === 'number' && typeof user.name === 'string'; } return false; } const UserComponent: React.FC = () => { const [user, setUser] = useState<UserData | null>(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 <div>Loading...</div>; return ( <div> <p>User ID: {user.id}</p> <p>Name: {user.name}</p> </div> ); };