Skip to content

Latest commit

ย 

History

History
375 lines (264 loc) ยท 7.57 KB

Advanced.md

File metadata and controls

375 lines (264 loc) ยท 7.57 KB

Back to Typescript Home Page

๐Ÿง  Advanced Topics ๐Ÿง ๐Ÿ’ก๐Ÿ“

These take you deeper into mastering TypeScriptโ€™s type system and writing safe, complex applications:


Advanced Types

๐Ÿ”น Mapped Types

Mapped types transform existing types into new ones.

type User = {
  name: string;
  age: number;
};

type ReadonlyUser = {
  readonly [K in keyof User]: User[K];
};
  • Use Case: Creating variations of existing types (e.g., Readonly, Partial).

๐Ÿ”น Conditional Types

Conditional types allow type logic based on conditions.

type IsString<T> = T extends string ? true : false;

type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
  • Use Case: Creating types that depend on other types.

๐Ÿ”น Indexed Access Types

Access a typeโ€™s properties using an index.

type User = { name: string; age: number };
type NameType = User["name"]; // string
  • Use Case: Extracting specific property types.

๐Ÿ”น Template Literal Types

Combine string literals with types.

type Route = `/users/${string}`;
const route: Route = "/users/123"; // Valid
  • Use Case: Defining dynamic string patterns.

Back to Top


Generics

Generics provide a way to create reusable, type-safe components.

๐Ÿ”น Generic Functions

function identity<T>(value: T): T {
  return value;
}

const num = identity<number>(42);
const str = identity<string>("Hello");
  • Use Case: Writing reusable functions with type safety.

๐Ÿ”น Generic Interfaces

interface Box<T> {
  value: T;
}

const box: Box<number> = { value: 42 };
  • Use Case: Creating flexible data structures.

๐Ÿ”น Generic Constraints

Restrict the types that can be used with generics.

function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

getLength("Hello"); // 5
getLength([1, 2, 3]); // 3
  • Use Case: Enforcing specific properties on generic types.

Back to Top


Declaration Files (.d.ts)

Declaration files provide type definitions for JavaScript libraries.

๐Ÿ”น Example:

// math.d.ts
declare module "math" {
  export function add(a: number, b: number): number;
}
  • Use Case: Adding type definitions for libraries without built-in TypeScript support.

Back to Top


Modules and Namespaces

๐Ÿ”น Modules

Modules use import and export to organize code.

// utils.ts
export function greet(name: string): string {
  return `Hello, ${name}`;
}

// main.ts
import { greet } from "./utils";
console.log(greet("Alice"));

๐Ÿ”น Namespaces

Namespaces group related code under a single name.

namespace Utils {
  export function greet(name: string): string {
    return `Hello, ${name}`;
  }
}

console.log(Utils.greet("Alice"));
  • Use Case: Namespaces are useful for organizing code in non-modular environments.

Back to Top


Type Operators: keyof, typeof, in, infer

๐Ÿ”น keyof

Extracts the keys of a type.

type User = { name: string; age: number };
type UserKeys = keyof User; // "name" | "age"

๐Ÿ”น typeof

Gets the type of a value.

const user = { name: "Alice", age: 25 };
type UserType = typeof user; // { name: string; age: number }

๐Ÿ”น in

Iterates over keys in a type.

type ReadonlyUser = {
  [K in keyof User]: User[K];
};

๐Ÿ”น infer

Infers a type within a conditional type.

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type Test = ReturnType<() => string>; // string

Back to Top


Discriminated Unions

Discriminated unions simplify working with multiple object types.

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function getArea(shape: Shape): number {
  if (shape.kind === "circle") {
    return Math.PI * shape.radius ** 2;
  } else {
    return shape.side ** 2;
  }
}

Back to Top


Recursive Types

Recursive types allow types to reference themselves.

type NestedArray<T> = T | NestedArray<T>[];

const arr: NestedArray<number> = [1, [2, [3]]];
  • Use Case: Defining deeply nested structures.

Back to Top


Handling this in Different Contexts

TypeScript provides better control over this.

class Counter {
  count = 0;

  increment(this: Counter): void {
    this.count++;
  }
}

const counter = new Counter();
counter.increment();
  • Use Case: Preventing incorrect this usage.

Back to Top


TypeScript with React (JSX/TSX & Props Typing)

TypeScript enhances React development with type safety.

๐Ÿ”น Typing Props:

type ButtonProps = {
  label: string;
  onClick: () => void;
};

const Button: React.FC<ButtonProps> = ({ label, onClick }) => (
  <button onClick={onClick}>{label}</button>
);

๐Ÿ”น Typing State:

const [count, setCount] = React.useState<number>(0);

Back to Top


Working with 3rd Party Libraries (@types)

Use @types packages for type definitions.

npm install --save-dev @types/lodash
import _ from "lodash";
_.chunk([1, 2, 3, 4], 2); // [[1, 2], [3, 4]]

Back to Top


Configuring tsconfig.json for Real-World Projects

๐Ÿ”น Example Configuration:

{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

Back to Top


Type Checking vs Runtime Checking

TypeScript performs type checking at compile time, but runtime errors can still occur.

๐Ÿ”น Example:

function divide(a: number, b: number): number {
  if (b === 0) throw new Error("Division by zero");
  return a / b;
}
  • Use Case: Combine TypeScript with runtime checks for robust applications.

Back to Top


Best Practices & Clean Code with TypeScript

  1. Enable Strict Mode: Use "strict": true in tsconfig.json.
  2. Use Type Inference: Let TypeScript infer types when possible.
  3. Avoid any: Use unknown or proper types instead.
  4. Use Utility Types: Simplify type transformations.
  5. Write Declaration Files: Add types for external libraries.

Back to Top