TypeScript
TypeScript

TypeScript

Tags
TypeScript
JavaScript
Web Dev
Node.js
Published
November 1, 2023
"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. let num: number = 5; let str: string = "Hello, TypeScript!"; let flag: boolean = true; let anyValue: any = "I can be anything!";
  1. Type Annotations: You can explicitly annotate variable types using a colon (:).
    1. let myVar: number; myVar = 42;
  1. Type Inference: TypeScript can often infer the type based on the assigned value.
    1. let x = 5; // TypeScript infers x as number
  1. Interfaces: Use interfaces to define the shape of objects or classes.
    1. interface Person { name: string; age: number; } let person: Person = { name: "Alice", age: 30 };
  1. Classes: TypeScript supports class-based object-oriented programming.
    1. 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. function add(a: number, b: number): number { return a + b; }
  1. Generics: Use generics for writing reusable and type-safe code.
    1. 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. let result: number | string; let personOrAnimal: Person & Animal;
  1. Enums: Enumerations allow you to define a set of named constants.
    1. 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. 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.
// 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.