🧼

TypeScript

"TypeScript, the guardian of order in the chaotic world of JavaScript, where clarity and safety reign supreme.”
  1. Basic Types: TypeScript includes several basic data types like number, string, boolean, and any. Use them to define variables and function parameters.
    1. typescriptCopy code let num: number = 5; let str: string = "Hello, TypeScript!"; let flag: boolean = true; let anyValue: any = "I can be anything!";
      Copy and SaveShare
      notion image
      notion image
  1. Type Annotations: You can explicitly annotate variable types using a colon (:).
    1. typescriptCopy code let myVar: number; myVar = 42;
  1. Type Inference: TypeScript can often infer the type based on the assigned value.
    1. typescriptCopy code let x = 5; // TypeScript infers x as number
  1. Interfaces: Use interfaces to define the shape of objects or classes.
    1. typescriptCopy code interface Person { name: string; age: number; } let person: Person = { name: "Alice", age: 30 };
  1. Classes: TypeScript supports class-based object-oriented programming.
    1. typescriptCopy code class Animal { constructor(public name: string) {} speak() { console.log(`My name is ${this.name}`); } } const dog = new Animal("Rover"); dog.speak();
  1. Function Types: Declare the types of function parameters and return values.
    1. typescriptCopy code function add(a: number, b: number): number { return a + b; }
  1. Generics: Use generics for writing reusable and type-safe code.
    1. typescriptCopy code function identity<T>(arg: T): T { return arg; }
  1. Union and Intersection Types: Create types that can be of multiple types (union) or must satisfy multiple conditions (intersection).
    1. typescriptCopy code let result: number | string; let personOrAnimal: Person & Animal;
  1. Enums: Enumerations allow you to define a set of named constants.
    1. typescriptCopy code enum Color { Red, Green, Blue, } let myColor: Color = Color.Red;
  1. Type Assertions: Use type assertions to tell TypeScript that you know more about a value's type than TypeScript does.
    1. typescriptCopy code let value: any = "This is a string"; let strLength: number = (value as string).length;
  1. Type Compatibility: TypeScript has a system for checking compatibility between types, which is often referred to as "duck typing."
  1. Modules: Organize your code into reusable modules using import and export.
typescriptCopy code // math.ts export function add(a: number, b: number): number { return a + b; } // app.ts import { add } from "./math";
 
 

Advanced Types

  1. Type Narrowing with typeof and instanceof: TypeScript allows you to narrow down types using typeof and instanceof to make more specific type assertions.
    1. function printIfNumber(value: number | string) { if (typeof value === 'number') { console.log(value.toFixed(2)); // Here, value is a number. } else { console.log(value.toUpperCase()); // Here, value is a string. } }
  1. Type Aliases: Type aliases let you create custom, reusable types.
    1. type Point = { x: number; y: number; }; const origin: Point = { x: 0, y: 0 };
  1. Mapped Types: Mapped types allow you to create new types based on the properties of existing types.
    1. type Options = { width: number; height: number; }; type ReadonlyOptions = { readonly [K in keyof Options]: Options[K] };
  1. Conditional Types: Conditional types enable you to create types based on conditions.
    1. type NonNullable<T> = T extends null | undefined ? never : T; type StrOrNum = string | number; type NonNullStrOrNum = NonNullable<StrOrNum>;
  1. Template Literal Types: Template literal types let you create new types by concatenating string literals.
    1. type Greeting = 'Hello, ' | 'Hi, '; type Language = 'English' | 'Spanish'; type Salutation = `${Greeting}${Language}`;
  1. Discriminated Unions: Discriminated unions use a common property to determine the specific type in a union.
    1. type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; sideLength: number }; function getArea(shape: Shape): number { if (shape.kind === 'circle') { return Math.PI * shape.radius ** 2; } else { return shape.sideLength ** 2; } }
  1. Function Overloads: Function overloads allow you to provide multiple type signatures for a function.
    1. function greet(name: string): string; function greet(names: string[]): string; function greet(names: string | string[]): string { if (Array.isArray(names)) { return `Hello, ${names.join(' and ')}!`; } else { return `Hello, ${names}!`; } }
  1. Key Remapping in Objects: You can remap keys in objects using mapped types.
    1. type AliasMap<T> = { [K in keyof T as `new_${K}`]: T[K] }; const original = { name: 'Alice', age: 30 }; const remapped: AliasMap<typeof original> = { new_name: 'Alice', new_age: 30 };
  1. Infer Keyword: The infer keyword lets you extract type information from generic types.
    1. type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; type Result = ReturnType<() => number>; // Result is number.
  1. Tuple Types: TypeScript allows you to define tuple types with a specific length and type for each element.
    1. type ThreeDCoordinates = [number, number, number]; const point: ThreeDCoordinates = [1, 2, 3];
 
  1. Type Definitions for External Libraries: TypeScript provides type definitions (.d.ts) for JavaScript libraries to enable type checking and auto-completion in your code. You can install these types using npm or yarn.
    1. # Install type definitions for a library npm install --save @types/library-name
  1. TypeScript Compiler (tsc): Use the TypeScript compiler (tsc) to transpile TypeScript code into JavaScript. First, install TypeScript globally or locally in your project:
    1. npm install -g typescript # Global installation # OR npm install typescript --save-dev # Local installation
      Create a TypeScript file (e.g., app.ts) and compile it:
      // app.ts const message: string = "Hello, TypeScript!"; console.log(message);
      Compile it using tsc:
      tsc app.ts
      This generates an app.js file that can be run in a Node.js environment or included in your web application.
  1. tsconfig.json: The tsconfig.json file configures your TypeScript project. Here's a simple example:
    1. { "compilerOptions": { "target": "ES6", "outDir": "./dist", "rootDir": "./src" } }
      This configuration specifies the target JavaScript version, output directory for compiled code, and source code directory. You can customize it further to suit your project.
  1. TypeScript Tooling: TypeScript-aware editors like Visual Studio Code provide excellent TypeScript support out of the box. You'll get features like autocompletion, type checking, and quick error feedback.
  1. Declaration Files: When you have a JavaScript library without type definitions, you can create a declaration file (.d.ts) to provide type information. For example, if you have a my-library.js:
    1. // my-library.js function add(a, b) { return a + b; }
      Create a corresponding my-library.d.ts declaration file:
      // my-library.d.ts declare function add(a: number, b: number): number;
      TypeScript will now recognize the types for the functions in my-library.js.
  1. Advanced Types: Explore advanced TypeScript types like mapped types, conditional types, and template literal types based on your project needs.

Async/await

Async/await is a feature in TypeScript (and JavaScript) that simplifies asynchronous code by allowing you to write it in a more synchronous style. It's particularly useful when dealing with asynchronous operations like network requests or file I/O. Here's an explanation with code snippets:

Using async and await

The async keyword is used to define a function as asynchronous. An asynchronous function returns a Promise implicitly. You can then use the await keyword inside an asynchronous function to pause its execution until a Promise is resolved.
async function fetchData() { try { const response = await fetch('<https://api.example.com/data>'); const data = await response.json(); return data; } catch (error) { console.error('Error:', error); throw error; } }
In this example:
  • fetchData is an asynchronous function.
  • We use await with fetch to wait for the network request to complete and get the response.
  • Then, we use await with response.json() to wait for the parsing of JSON data.
  • If there's an error, we catch it and handle it gracefully.

Error Handling

Async/await allows for more natural error handling using try-catch blocks. Any errors thrown in the async function can be caught and managed in the catch block.

Use Cases

  1. Fetching Data from APIs: Async/await is commonly used for making HTTP requests to APIs and handling the responses.
  1. Reading/Writing Files: When working with Node.js, you can use async/await for reading and writing files asynchronously.
  1. Database Operations: Async/await is useful for interacting with databases, where operations can be asynchronous.
  1. Promisified Functions: You can use async/await to simplify the use of functions that return Promises, making your code more readable.
  1. Sequential Execution: Async/await allows you to write asynchronous code in a sequential, easy-to-read manner.
  1. Concurrent Execution: You can also use async/await in combination with Promise.all for concurrent execution of multiple asynchronous tasks.

Browser Support

Async/await is widely supported in modern browsers and is available in Node.js. However, if you're targeting older environments, consider using a transpiler like Babel to ensure compatibility.
Remember that async/await is just a syntax improvement for working with Promises. It doesn't change the fundamental asynchronous nature of JavaScript/TypeScript, so you still need to be mindful of potential issues like callback hell or concurrency. Async/await is a valuable tool for making your asynchronous code more readable and maintainable.

Decorators

Decorators allow you to add metadata and behavior to classes and class members. Here's a simple example of a class decorator:

Basic Decorator Example:

function logClass(target: any) { console.log(`Class ${target.name} is being constructed.`); } @logClass class MyClass { constructor() { console.log('MyClass constructor'); } } // Output: // Class MyClass is being constructed. // MyClass constructor
In this example, logClass is a decorator function that logs a message when a class is constructed. The @logClass syntax is used to apply the decorator to the MyClass class.

Decorator Factories:

Decorators can accept arguments and return functions, allowing you to create decorator factories.
function logMethod(prefix: string) { return function (target: any, key: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`${prefix} - Before method call`); const result = originalMethod.apply(this, args); console.log(`${prefix} - After method call`); return result; }; }; } class Example { @logMethod('Example') myMethod() { console.log('Inside myMethod'); } } const instance = new Example(); instance.myMethod(); // Output: // Example - Before method call // Inside myMethod // Example - After method call
Here, logMethod is a decorator factory that accepts a prefix argument. The factory returns a decorator function that logs messages before and after the method call.

Use Cases for Decorators:

  1. Logging and Debugging: Decorators can log method calls, measure execution times, and help with debugging.
  1. Authentication and Authorization: You can use decorators to restrict access to certain methods or routes based on user roles or authentication status.
  1. Validation: Decorators can validate method arguments or object properties.
  1. Memoization: Decorators can cache the results of function calls to improve performance.
  1. Dependency Injection: Frameworks like Angular use decorators for dependency injection and service registration.
  1. Route Handling (in Express.js): You can use decorators to define routes and middleware functions in Express.js controllers.
  1. Data Validation (in Mongoose): In Mongoose (a MongoDB library for Node.js), decorators can validate and define data schema for models.
  1. Redux Middleware: In Redux, decorators can be used to create custom middleware for actions.
  1. State Management (in MobX): MobX uses decorators to define observable properties and actions that update the state.
  1. Aspect-Oriented Programming: Decorators enable aspect-oriented programming by separating concerns like logging, caching, and authentication from the core business logic.
Keep in mind that decorators are not unique to TypeScript; they are part of the ECMAScript proposal. In TypeScript, they are well-suited for use with class-based programming and are widely used in frameworks like Angular and Nest.js. However, they should be used judiciously, as overuse can lead to complex and hard-to-maintain code.