evantian.me

TypeScript Exercises

May 4, 2020 • ☕️ 4 min read

Recently, I found a repository named typescript-exercises and walk through the exercises it offers, they’re pretty useful for me. So I’ll record here my solutions and the things I learnt from.

exercise-00

source address

This one is pretty easy, all we need to do is defining an interface User:

interface User {  name: string;  age: number;  occupation: string;}
const users: User[] = [
  // ...
]

function logPerson(user: User) {
  console.log(` - ${chalk.green(user.name)}, ${user.age}`)
}

exercise-01

source address

Here, we need to define a union type that can be a User or Admin type:

interface User {
  name: string;
  age: number;
  occupation: string;
}

interface Admin {
  name: string;
  age: number;
  role: string;
}

type Person = User | Admin
const persons: Person[] = {
  // both user and admins are here
}

function logPerson(person: Person) {
  console.log(` - ${chalk.green(person.name)}, ${person.age}`)
}

Advanced Types - union types

Union types is a very powerful feature. Consider this:

function isAdmin(person: User | Admin): boolean {
  return role in Person
}

if (isAdmin(somePerson)) {
  // error, TypeScript doesn't know if somePerson has a property named role
  somePerson.role
}

With union type and user-defined type guard:

function isAdmin(person: User | Admin): person is Admin {
  return 'role' in person
}

if (isAdmin(somePerson)) {
  // here we can get role directly by somePerson.role
  // because TypeScript help us narrow down somePerson's type => Admin
}

exercise-02

source address

This one we need to fix logPerson by letting TypeScript know what type it is before access it’s properties:

function logPerson(person: Person) {
  let additionalInformation: string;
  if ('role' in person) {    additionalInformation = person.role;
  } else {
    additionalInformation = person.occupation;
  }
  // ...
}

exercise-03

source address

Yup, this time user-defined type guard is gonna show up:

interface User {
  type: 'user';
  name: string;
  age: number;
  occupation: string;
}

interface Admin {
  type: 'admin';
  name: string;
  age: number;
  role: string;
}

type Person = User | Admin

function isUser(person: Person): person is User {  return person.type === 'user'
}

function isAdmin(person: Person): person is Admin {  return person.type === 'admin'
}

function logPerson(person: Person) {
  let additionalInformation: string = '';
  if (isAdmin(person)) {
      additionalInformation = person.role;
  }
  if (isUser(person)) {
      additionalInformation = person.occupation;
  }
  // ...
}

exercise-04

source address

This task asks us to type arguements of filterUsers, criteria has some values of Users, so Partial<T> is what we want:

type Partial<T> = {
  [P in key of T]?: T[P]
}
function filterUsers(persons: Person[], criteria: Partial<User>): User[] {
  return persons.filter(isUser).filter((user) => {
    let criteriaKeys = Object.keys(criteria) as (keyof User)[];
    return criteriaKeys.every((fieldName) => {
      return user[fieldName] === criteria[fieldName];
    });
  });
}

exercise-05

source address

We need overload to return different types based on different arguments:

type PersonType = Admin['type'] | User['type']

function filterPersons(
  persons: Person[],
  personType: Admin['type'],
  criteria: Partial<Admin>,
): Admin[];

function filterPersons(
  persons: Person[],
  personType: User['type'],
  criteria: Partial<User>,
): User[];

function filterPersons(
  persons: Person[],
  personType: PersonType,
  criteria: Partial<Admin | User>,
) {
  return persons
    .filter((person) => person.type === personType)
    .filter((person) => {
      let criteriaKeys = Object.keys(criteria) as (keyof Person)[];
      return criteriaKeys.every((fieldName) => {
        return person[fieldName] === criteria[fieldName];
      });
    });
}

let usersOfAge23: User[] = filterPersons(persons, 'user', { age: 23 });
let adminsOfAge23: Admin[] = filterPersons(persons, 'admin', { age: 23 });

exercise-06

source address

Obviously, we need generics to type function swap:

function swap<T1, T2>(v1: T1, v2: T2): [T2, T1] {
  return [v2, v1]
}

exercise-07

source address

To type PowerUser, we need to combine Admin and User except property ‘type’, then add its own ‘type’:

type PowerUser = Omit<Admin & User, 'type'> & {
  type: 'powerUser';
}