The Ultimate TypeScript Guide: From Novice to Expert
TypeScript
JavaScript
Programming
Web Development

The Ultimate TypeScript Guide: From Novice to Expert

FS
Fernand SOUALO
·
12 min read

The Ultimate TypeScript Guide: From Novice to Expert

Table of Contents#

  1. Introduction
  2. Basic Types
  3. Advanced Types
  4. Functions
  5. Classes and Interfaces
  6. Generics
  7. Type Manipulation
  8. Modules and Namespaces
  9. Decorators
  10. Advanced Patterns
  11. TypeScript Compiler and Configuration
  12. TypeScript with React
  13. Testing in TypeScript
  14. Performance Optimization
  15. Real-World Project: Building a Type-Safe API Client

Introduction #

TypeScript is a powerful superset of JavaScript that adds static typing and other features to enhance developer productivity and code quality. This guide will take you from the basics to advanced concepts, providing a comprehensive understanding of TypeScript.

Génération du diagramme…
TypeScript est un superset de JavaScript — tout JS valide est du TS valide, mais TS ajoute le typage statique

Basic Types #

TypeScript provides several basic types to help you describe the shape of your data:

TypeScript
// Boolean
let isDone: boolean = false;

// Number
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;

// String
let color: string = "blue";
color = "red";

// Array
let list: number[] = [1, 2, 3];
let fruits: Array<string> = ["apple", "banana", "orange"];

// Tuple
let x: [string, number];
x = ["hello", 10]; // OK
// x = [10, "hello"]; // Error

// Enum
enum Color {
  Red,
  Green,
  Blue,
}
let c: Color = Color.Green;

// Any
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

// Void
function warnUser(): void {
  console.log("This is my warning message");
}

// Null and Undefined
let u: undefined = undefined;
let n: null = null;

// Never
function error(message: string): never {
  throw new Error(message);
}

// Object
let obj: object = { key: "value" };

Advanced Types #

TypeScript offers advanced type features for more complex scenarios:

Union Types#

TypeScript
function padLeft(value: string, padding: string | number) {
  // ...
}

let indentedString = padLeft("Hello world", 4); // Works

Intersection Types#

TypeScript
interface ErrorHandling {
  success: boolean;
  error?: { message: string };
}

interface ArtworksData {
  artworks: { title: string }[];
}

type ArtworksResponse = ErrorHandling & ArtworksData;

const response: ArtworksResponse = {
  success: true,
  artworks: [{ title: "Mona Lisa" }],
};

Type Aliases#

TypeScript
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;

function getName(n: NameOrResolver): Name {
  if (typeof n === "string") {
    return n;
  } else {
    return n();
  }
}

Literal Types#

TypeScript
type Easing = "ease-in" | "ease-out" | "ease-in-out";

function animate(dx: number, dy: number, easing: Easing) {
  // ...
}

animate(0, 0, "ease-in");
// animate(0, 0, "linear"); // Error: Argument of type '"linear"' is not assignable to parameter of type 'Easing'.

Index Types#

TypeScript
function pluck<T, K extends keyof T>(o: T, propertyNames: K[]): T[K][] {
  return propertyNames.map((n) => o[n]);
}

interface Car {
  manufacturer: string;
  model: string;
  year: number;
}

let taxi: Car = {
  manufacturer: "Toyota",
  model: "Camry",
  year: 2014,
};

let makeAndModel: string[] = pluck(taxi, ["manufacturer", "model"]);

Functions #

TypeScript enhances JavaScript functions with type annotations and more:

Function Types#

TypeScript
function add(x: number, y: number): number {
  return x + y;
}

let myAdd: (x: number, y: number) => number = function (
  x: number,
  y: number
): number {
  return x + y;
};

Optional and Default Parameters#

TypeScript
function buildName(firstName: string, lastName?: string) {
  if (lastName) return firstName + " " + lastName;
  else return firstName;
}

function buildName2(firstName: string, lastName = "Smith") {
  return firstName + " " + lastName;
}

Rest Parameters#

TypeScript
function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

Function Overloads#

TypeScript
function padding(all: number);
function padding(topAndBottom: number, leftAndRight: number);
function padding(top: number, right: number, bottom: number, left: number);
function padding(a: number, b?: number, c?: number, d?: number) {
  if (b === undefined && c === undefined && d === undefined) {
    b = c = d = a;
  } else if (c === undefined && d === undefined) {
    c = a;
    d = b;
  }
  return {
    top: a,
    right: b,
    bottom: c,
    left: d,
  };
}

Classes and Interfaces #

TypeScript provides full support for classes and interfaces:

Classes#

TypeScript
class Animal {
  private name: string;
  constructor(theName: string) {
    this.name = theName;
  }
  move(distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

class Snake extends Animal {
  constructor(name: string) {
    super(name);
  }
  move(distanceInMeters = 5) {
    console.log("Slithering...");
    super.move(distanceInMeters);
  }
}

Interfaces#

TypeScript
interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

class Clock implements ClockInterface {
  currentTime: Date = new Date();
  setTime(d: Date) {
    this.currentTime = d;
  }
  constructor(h: number, m: number) {}
}

Generics #

Generics allow you to create reusable components:

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

let output = identity<string>("myString");

class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
};

Generic Constraints#

TypeScript
interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length); // Now we know it has a .length property, so no more error
  return arg;
}

Type Manipulation #

TypeScript provides powerful ways to manipulate and transform types:

Mapped Types#

TypeScript
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Partial<T> = {
  [P in keyof T]?: T[P];
};

interface Person {
  name: string;
  age: number;
}

type ReadonlyPerson = Readonly<Person>;

Conditional Types#

TypeScript
type NonNullable<T> = T extends null | undefined ? never : T;

type ExtractType<T, U> = T extends U ? T : never;

type StringOrNumber = ExtractType<string | number | boolean, string | number>;

Infer Keyword#

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

function f() {
  return { x: 10, y: 3 };
}
type FReturnType = ReturnType<typeof f>; // { x: number, y: number }

Modules and Namespaces #

TypeScript supports both ES modules and its own namespace system:

ES Modules#

TypeScript
// math.ts
export function add(x: number, y: number): number {
  return x + y;
}

// main.ts
import { add } from "./math";
console.log(add(1, 2));

Namespaces#

TypeScript
namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean;
  }

  const lettersRegexp = /^[A-Za-z]+$/;
  const numberRegexp = /^[0-9]+$/;

  export class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
      return lettersRegexp.test(s);
    }
  }

  export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
      return s.length === 5 && numberRegexp.test(s);
    }
  }
}

let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

Decorators #

Decorators provide a way to add both annotations and a meta-programming syntax for class declarations and members:

TypeScript
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return "Hello, " + this.greeting;
  }
}

Advanced Patterns #

Mixins#

TypeScript
// Disposable Mixin
class Disposable {
  isDisposed: boolean;
  dispose() {
    this.isDisposed = true;
  }
}

// Activatable Mixin
class Activatable {
  isActive: boolean;
  activate() {
    this.isActive = true;
  }
  deactivate() {
    this.isActive = false;
  }
}

class SmartObject implements Disposable, Activatable {
  constructor() {
    setInterval(
      () => console.log(this.isActive + " : " + this.isDisposed),
      500
    );
  }

  interact() {
    this.activate();
  }

  // Disposable
  isDisposed: boolean = false;
  dispose: () => void;
  // Activatable
  isActive: boolean = false;
  activate: () => void;
  deactivate: () => void;
}
applyMixins(SmartObject, [Disposable, Activatable]);

let smartObj = new SmartObject();
setTimeout(() => smartObj.interact(), 1000);

////////////////////////////////////////
// In your runtime library somewhere
////////////////////////////////////////

function applyMixins(derivedCtor: any, baseCtors: any[]) {
  baseCtors.forEach((baseCtor) => {
    Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
      Object.defineProperty(
        derivedCtor.prototype,
        name,
        Object.getOwnPropertyDescriptor(baseCtor.prototype, name)
      );
    });
  });
}

Builder Pattern with Method Chaining#

TypeScript
class RequestBuilder {
  private data: object | null = null;
  private method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" = "GET";
  private url: string = "";

  setMethod(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"): this {
    this.method = method;
    return this;
  }

  setData(data: object): this {
    this.data = data;
    return this;
  }

  setURL(url: string): this {
    this.url = url;
    return this;
  }

  send(): Promise<Response> {
    return fetch(this.url, {
      method: this.method,
      body: this.data ? JSON.stringify(this.data) : null,
      headers: {
        "Content-Type": "application/json",
      },
    });
  }
}

// Usage
new RequestBuilder()
  .setMethod("POST")
  .setURL("https://api.example.com/data")
  .setData({ name: "John Doe" })
  .send()
  .then((response) => response.json())
  .then((data) => console.log(data));

TypeScript Compiler and Configuration #

Understanding the TypeScript compiler (tsc) and its configuration options is crucial:

tsconfig.json#

JSON
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": ["dom", "es2015"],
    "strict": true,
    "esModuleInterop": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.spec.ts"]
}

Compiler API#

TypeScript
import * as ts from "typescript";

function compile(fileNames: string[], options: ts.CompilerOptions): void {
  let program = ts.createProgram(fileNames, options);
  let emitResult = program.emit();

  let allDiagnostics = ts
    .getPreEmitDiagnostics(program)
    .concat(emitResult.diagnostics);

  allDiagnostics.forEach((diagnostic) => {
    if (diagnostic.file) {
      let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(
        diagnostic.start!
      );
      let message = ts.flattenDiagnosticMessageText(
        diagnostic.messageText,
        "\n"
      );
      console.log(
        `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`
      );
    } else {
      console.log(
        ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")
      );
    }
  });

  let exitCode = emitResult.emitSkipped ? 1 : 0;
  console.log(`Process exiting with code '${exitCode}'.`);
  process.exit(exitCode);
}

compile(process.argv.slice(2), {
  noEmitOnError: true,
  noImplicitAny: true,
  target: ts.ScriptTarget.ES5,
  module: ts.ModuleKind.CommonJS,
});

TypeScript with React #

TypeScript integrates seamlessly with React, providing type safety for props, state, and more:

TypeScript
import React, { useState, useEffect } from "react";

interface User {
  id: number;
  name: string;
  email: string;
}

interface UserListProps {
  title: string;
}

const UserList: React.FC<UserListProps> = ({ title }) => {
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState<boolean>(true);

  useEffect(() => {
    fetchUsers();
  }, []);

  const fetchUsers = async () => {
    try {
      const response = await fetch("https://api.example.com/users");
      const data: User[] = await response.json();
      setUsers(data);
      setLoading(false);
    } catch (error) {
      console.error("Error fetching users:", error);
      setLoading(false);
    }
  };

  return (
    <div>
      <h1>{title}</h1>
      {loading ? (
        <p>Loading...</p>
      ) : (
        <ul>
          {users.map((user) => (
            <li key={user.id}>
              {user.name} ({user.email})
            </li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default UserList;

Testing in TypeScript #

Testing TypeScript code involves using testing frameworks like Jest along with TypeScript-specific tools:

TypeScript
// math.ts
export function add(a: number, b: number): number {
  return a + b;
}

// math.test.ts
import { add } from "./math";

describe("add function", () => {
  it("should add two numbers correctly", () => {
    expect(add(1, 2)).toBe(3);
    expect(add(-1, 1)).toBe(0);
    expect(add(5, 5)).toBe(10);
  });

  it("should return NaN for invalid inputs", () => {
    expect(add(1, NaN)).toBeNaN();
    expect(add(NaN, 1)).toBeNaN();
  });
});

Performance Optimization #

TypeScript can help with performance optimization through proper typing and compiler optimizations:

TypeScript
// Using const assertions for better performance
const config = {
  endpoint: "https://api.example.com",
  apiKey: "your-api-key",
} as const;

// Type-based optimizations
type Vec2D = [number, number];

function addVectors(v1: Vec2D, v2: Vec2D): Vec2D {
  return [v1[0] + v2[0], v1[1] + v2[1]];
}

// Performance-critical loop
function sumArray(arr: number[]): number {
  let sum = 0;
  for (let i = 0; i < arr.length; i++) {
    sum += arr[i];
  }
  return sum;
}

// Using generics for flexible, type-safe functions
function quickSort<T>(arr: T[], compare: (a: T, b: T) => number): T[] {
  if (arr.length <= 1) return arr;

  const pivot = arr[Math.floor(arr.length / 2)];
  const left = arr.filter((x) => compare(x, pivot) < 0);
  const middle = arr.filter((x) => compare(x, pivot) === 0);
  const right = arr.filter((x) => compare(x, pivot) > 0);

  return [...quickSort(left, compare), ...middle, ...quickSort(right, compare)];
}

Real-World Project: Building a Type-Safe API Client #

Let's build a type-safe API client using TypeScript, demonstrating many of the concepts we've covered:

TypeScript
// api-types.ts
export interface User {
  id: number;
  name: string;
  email: string;
}

export interface Post {
  id: number;
  title: string;
  body: string;
  userId: number;
}

export interface Comment {
  id: number;
  postId: number;
  name: string;
  email: string;
  body: string;
}

// api-client.ts
import axios, { AxiosInstance, AxiosResponse } from "axios";
import { User, Post, Comment } from "./api-types";

class APIClient {
  private client: AxiosInstance;

  constructor(baseURL: string) {
    this.client = axios.create({
      baseURL,
      headers: {
        "Content-Type": "application/json",
      },
    });
  }

  private async get<T>(url: string): Promise<T> {
    const response: AxiosResponse<T> = await this.client.get(url);
    return response.data;
  }

  private async post<T>(url: string, data: any): Promise<T> {
    const response: AxiosResponse<T> = await this.client.post(url, data);
    return response.data;
  }

  async getUsers(): Promise<User[]> {
    return this.get<User[]>("/users");
  }

  async getUser(id: number): Promise<User> {
    return this.get<User>(`/users/${id}`);
  }

  async getPosts(): Promise<Post[]> {
    return this.get<Post[]>("/posts");
  }

  async getPost(id: number): Promise<Post> {
    return this.get<Post>(`/posts/${id}`);
  }

  async getComments(postId: number): Promise<Comment[]> {
    return this.get<Comment[]>(`/posts/${postId}/comments`);
  }

  async createPost(post: Omit<Post, "id">): Promise<Post> {
    return this.post<Post>("/posts", post);
  }
}

// Usage
const apiClient = new APIClient("https://jsonplaceholder.typicode.com");

async function fetchAndDisplayUserPosts(userId: number) {
  try {
    const user = await apiClient.getUser(userId);
    console.log(`Fetching posts for user: ${user.name}`);

    const posts = await apiClient.getPosts();
    const userPosts = posts.filter((post) => post.userId === userId);

    for (const post of userPosts) {
      console.log(`Post: ${post.title}`);
      const comments = await apiClient.getComments(post.id);
      console.log(`Comments: ${comments.length}`);
    }
  } catch (error) {
    console.error("Error fetching data:", error);
  }
}

fetchAndDisplayUserPosts(1);

This project demonstrates:

  • Type definitions for API responses
  • A generic API client with type-safe methods
  • Error handling
  • Async/await usage
  • Filtering and mapping of typed arrays

Conclusion#

This comprehensive guide covers the breadth and depth of TypeScript, from basic concepts to advanced patterns and real-world applications. By mastering these concepts, you'll be well-equipped to build robust, scalable, and maintainable applications using TypeScript.

Additional Resources#

Cet article vous a été utile ?

12 min read
16 vues
0 j'aime
0 partages