Typescript Crash Course

ยท 6 minute read

TypeScript, a superset of JavaScript, enhances code quality with static typing. It brings robustness to web development, empowering developers with modern features.

Starting a typescript project with the CLI ๐Ÿ”—

npm init -y to create the package.json file.

Install the typescript compiler - npm i --save-dev typescript as a dev dependency

npx tsc --init to run the typescript compiler and create a config file

Starting a typescript project with a bundle - VITE ๐Ÿ”—

npm create vite@latest .

Assignment Types ๐Ÿ”—

let a: number = 5;
let b: string = "asdf";
let a: number[] = []; // array type
let a; // any type

Objects ๐Ÿ”—

const user: { name: string; age: number; isProgrammer: boolean } = {
  name: "John",
  age: 32,
  isProgrammer: true,
};

We could also make one of the types optional using ? after the variable identifier

const user: { name: string; age: number; isProgrammer?: boolean } = {
  name: "John",
  age: 32,
};

Type ๐Ÿ”—

A type is declared with capital case - PersonName

type Person = { name: string; age: number; isProgrammer?: boolean };

const user: Person = {
  name: "John",
  age: 32,
};

Interface ๐Ÿ”—

An interface is more or less like type, but it has a few differences

  • It has no equal sign - interface Person {....}
  • It has to be an object
  • It’s not flexible like type where it can be assigned to other types e.g type Person = number
interface Person {
  name: string;
  age: number;
  isProgrammer?: boolean;
}

Functions ๐Ÿ”—

A function takes parameters with types and returns a type (implicitly) too.

function printPerson (name: string, age: number) {
  return ()
} //This code returns any type, but the arguments when calling the funciton msut be a string and a number.

function printPerson (person: {name: string}) {
  return (person.name)
}

Void Type ๐Ÿ”—

Unless explicitly stated that a function returns undefined, a function that doesn’t return anything returns void by defauly.

function printPerson(name: string, age: number) {
  console.log(age);
  return;
}

Optional Parameters ๐Ÿ”—

Uses the ? to denote parameters that are optional when passing arguments.

function printPerson(name: string, age?: number) {
  console.log(age);
  return;
}

Destructured Parameters ๐Ÿ”—

type options = { debugMode: boolean };

function test(age: number, { debugMode = false }: options) {
  console.log(age, debugMode);
}
test(18, { debugMode: true });

Rest Parameters ๐Ÿ”—

It’s always important to use an array type when passing a rest parameter

function test(...nums: number[]) {
  console.log(nums);
}
test(1, 3, 4, 5, 6, 7, 8, 9, 0);

Functions as Variables ๐Ÿ”—

function test(a: number, b: number, cb: (x: number, y: number) => void) {
  cb(a, b);
}

test(3, 8, (x, y) => {
  console.log(x * y);
});

More Types ๐Ÿ”—

Union Type ๐Ÿ”—

We can have a union of types

type name = string | number;

function sayHello(name: name) {
  console.log(`Hello ${name}`);
}

sayHello("Thanni");

We can also have a union of defined values

type name = "user" | "username";

function sayHello(name: name) {
  console.log(`Hello ${name}`);
}

sayHello("user");

We can also have a union of unions

Intersection Type ๐Ÿ”—

An intersection is the opposite of union, using the & sign

type Person = {
  user: string;
  age: number;
};

type PersonWithID = Person & { id: number };

const person: PersonWithID = { user: "John", age: 30, id: 1 };

console.log(person);

Read Only ๐Ÿ”—

type NumberArray = readonly number[];

const newNum: NumberArray = [1, 2, 3, 4, 5];

KeyOf ๐Ÿ”—

type Person = {
  user: string;
  age: number;
};

const person = setPerson("user", { user: "Thanni", age: 25 });

function setPerson(key: keyof Person, Person: Person) {
  console.log(Person[key]);
}

TypeOf ๐Ÿ”—

const user = {
  user: "Thanni",
  age: 25,
};

const newUser: typeof user[] = [];

newUser.push(user);
newUser.push({ user: "John", age: 30 });

console.log(newUser);

Index Type ๐Ÿ”—

Allows you to access the types of properties of other types you’ve already created.

We could assign types by accessing type values using their indexes

type User = {
  user: "Thanni";
  age: 25;
  skillLevel: "Beginner" | "Intermediate" | "Advanced";
};

function printSkillLevel(skillLevel: User["skillLevel"]) {
  console.log(skillLevel);
}

Accessing types as object types

type User = {
  user: string;
  age: 25;
  skillLevel: "Beginner" | "Intermediate" | "Advanced";
};

type groupSkillLevel = {
  [index: string]: User[];
};

// or

type User = {
  user: string;
  age: 25;
  skillLevel: "Beginner" | "Intermediate" | "Advanced";
};

type groupSkillLevel = {
  [index in User["skillLevel"]]: User[];
};

Const ๐Ÿ”—

In Ts, let a = 1 - the type inferred as a number. However, const a = 1 - the type is not inferred at all. Behind the scenes, it is denoted as const a = 1 as const. The same thing applies to let a = 1 as const

Tuple ๐Ÿ”—

type Tuple = [string, number, boolean];

const a: Tuple = ["Thanni", 25, true];

Generics ๐Ÿ”—

Generics helps us be more precise about the output type. It’s encapsulated in angle brackets <type>

type APIResponse<Tdata> = {
  status: number;
  type: string;
  data: Tdata;
};

const response1: APIResponse<{ name: string }> = {
  status: 200,
  type: "good",
  data: { name: "Thanni" },
};

const response2: APIResponse<string[]> = {
  status: 200,
  type: "good",
  data: ["Thanni", "Thanni"],
};

They can also have default values

type APIResponse<Tdata = { name: string }> = {
  status: number;
  type: string;
  data: Tdata;
};

const response1: APIResponse = {
  status: 200,
  type: "good",
  data: { name: "Thanni" },
};

If we want the generics to be restricted to only one type, we use the extend keyword. In the example below, the generic must only be an object.

type APIResponse<Tdata extends object> = {
  status: number;
  type: string;
  data: Tdata;
};

const response1: APIResponse<{ name: string }> = {
  status: 200,
  type: "good",
  data: { name: "Thanni" },
};

Also Array<number> is the same as number[]. The same applies to other types we’ve discussed earlier.

Pick and Omit ๐Ÿ”—

They help select or deselect certain types in a Type without having to create a new Type.

type ToDo = {
  id: number;
  task: string;
  isCOmpleted: boolean;
};

type ToDoPreview = Omit<ToDo, "isCompleted">;
type ToDoActive = Pick<ToDo, "id" | "task">;

Partial and Required ๐Ÿ”—

type ToDo = {
  id: number;
  task: string;
  isCompleted: boolean;
};

type PartialToDo = Partial<ToDo>; *// all properties are optional*
type RequiredToDo = Required<ToDo>; *// all properties are required*

ReturnType and Parameters ๐Ÿ”—

function add(a: number, b: number) {
  return a + b;
}

type Add = ReturnType<typeof add>;
type Params = Parameters<typeof add>;

Record Type ๐Ÿ”—

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

type UserRecord = Record<keyof User, string>;

Awaited ๐Ÿ”—

async function getPromise() {
  return "Hello World";
}

type Value = Awaited<ReturnType<typeof getPromise>>;

Basic Type Guard ๐Ÿ”—

type User = {
  name: string;
  age: number;
  gender: "male" | "female" | "other";
  dueDate: Date | string;
};

function getUser(user: User) {
  if (user.dueDate instanceof Date) {
    return user.dueDate.toISOString();
  }

  console.log(user.dueDate);
}

Never Type ๐Ÿ”—

The code below checks if there’s any other type of priority that is left unattended to.

type ToDo = {
  title: string;
  description: string;
  priority: "high" | "medium" | "low";
  isComplete: boolean;
};

function extendToDo(todo: ToDo) {
  switch (todo.priority) {
    case "high":
      return todo;
    case "medium":
      return todo;
    case "low":
      return todo;
    default:
      const exCheck: never = todo.priority;
      return exCheck;
  }
}

Unknown Type ๐Ÿ”—

function func(data: unknown) {
  if (typeof data === "string") {
    console.log("string");
    return;
  }
}