Skip to content

Typescript: (1) Basic Types

Mithi Sevilla edited this page Apr 9, 2021 · 8 revisions

Typescript Links

Javascript Types

  • primitive types (string, number, boolean, bigint, null, undefined, symbol) + object
  • loosely typed

Type annotations

  • Typescript doesn't exist at runtime so it won't do time checking at runtime
  • annotations are removed during the transpilation process
let score: number

console.log(score)
// Variable 'score' is used before being assigned.
let score: number
score = 10
console.log(score) // no error
function add(a: number, b: number): number {
  return a + b
}

const multiply = (a: number, b: number): number => a * b

function subtract(a: number, b?: number): number {
  return a - (b || 0)
}

Type Inference

  • can infer types most of the time
  • can infer DateConstructor type
let dateOfBirth = new Date(1990, 4, 7)

// type of firstName is "bob"
const firstName = "bob"
// type of full name is string
let fullName = "bob"

// type of counter is any
let counter
counter = 10

// can infer that return type of add is a number
// no need to annotate
const add = (a: number, b: number) => a + b 

// type of 10 is number
const ten = add(5, 5)

// return type is any
const addTen(a) => a + 10
// fourteen is of type any
const fourteen = addTen(4)

Any

  • if variable is of type any, typescript doesn't do type checking on it
  • we rarely need to use it

Void

  • when function doesn't return anything, it's return value is of type void
  • a variable marked as void can only hold undefined or null
  • void is only really useful for function return types

Never

  • used to represent a type of value that will never occur
  • we can use it to flag areas of code that shouldn't be reached explicitly
function outputMessage(message: string) {
  if(typeof message === "string") {
    console.log(message)
  } else {
    let invalid = message // invalid is of type message
    console.error(invalid)
  }
}

Unknown

  • Similar to any but this one is typesafe
function add(a: unknown, b: unknown) {
  return a + b
}
// error: Object is of type "unknown"
// errors are raised because variables of type unknown
// can't be added together
// this works
function add(a: unknown, b: unknown) {
  if(typeof a === "number" && typeof b === "number") {
    return a + b
  }
  return 0
}

Type safe data fetching

async function getData(path: string): Promise<unknown> {
  const response = await fetch(path)
  return await response.json()
}

type Person = { id: string; name: string }

// the return type: person is Person
// is a type predicate
// a special return type that typescript compiler
// uses to know what type a particular value is
function isPerson(person: any): person is Person {
  return "id" in person && "name" in person
}

async function getPerson(): Promise<Person | null> {
  const person = await getData("/person/1")
  if(person && isPerson(person)) {
    return person
  }
  return null
}

Type assertions

getAge() as number and <number>getAge() are the same

// not working as you might have expected
// but this is how typescript works as of writing
const getAge =  ()  :  any  =>  {  return  "6"  }
const age:  number  = getAge()  as  number  +  2
console.log(age) // returns "62"

Tricks

Filter by either name, age, or occupation but NOT by type

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

export function filterUsers(persons: Person[], criteria: Partial<Omit<User, 'type'>>): User[] {
    return persons.filter(isUser).filter((user) => {
        const criteriaKeys = Object.keys(criteria) as (keyof Omit<User, 'type'>)[];
        return criteriaKeys.every((fieldName) => {
            return user[fieldName] === criteria[fieldName];
        });
    });
}

Exercise:

Fix typing for the filterPersons so that it can filter users
and return User[] when personType='user' and return Admin[]
when personType='admin'. Also filterPersons should accept
partial User/Admin type according to the personType.
`criteria` argument should behave according to the
`personType` argument value. `type` field is not allowed in
the `criteria` field.

Higher difficulty bonus exercise:

Implement a function `getObjectKeys()` which returns more
convenient result for any argument given, so that you don't
need to cast it.

let criteriaKeys = Object.keys(criteria) as (keyof User)[];
-->
let criteriaKeys = getObjectKeys(criteria);

https://www.typescriptlang.org/docs/handbook/functions.html#overloads

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

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

export type Person = User | Admin;

export const persons: Person[] = [
    { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' },
    { type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' },
    { type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' },
    { type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' },
    { type: 'user', name: 'Wilson', age: 23, occupation: 'Ball' },
    { type: 'admin', name: 'Agent Smith', age: 23, role: 'Anti-virus engineer' }
]

const getObjectKeys = <T>(obj: T) => Object.keys(obj) as (keyof T)[];

export function filterPersons(persons: Person[], personType: 'user', criteria: Partial<Omit<User, 'type'>>): User[]
export function filterPersons(persons: Person[], personType: 'admin', criteria: Partial<Omit<Admin, 'type'>>): Admin[]

export function filterPersons(persons: Person[], personType: string, criteria: Partial<Person>): Person[] {
    return persons
        .filter((person) => person.type === personType)
        .filter((person) => {
            let criteriaKeys = getObjectKeys(criteria)
            return criteriaKeys.every((fieldName) => {
                return person[fieldName] === criteria[fieldName];
            });
        });
}

export const usersOfAge23 = filterPersons(persons, 'user', { age: 23 });
export const adminsOfAge23 = filterPersons(persons, 'admin', { age: 23 });

Below is typescript's official example but to be honest I won't implement this functionality this way

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x: any): any {
  // Check to see if we're working with an object/array
  // if so, they gave us the deck and we'll pick the card
  if (typeof x == "object") {
    let pickedCard = Math.floor(Math.random() * x.length);
    return pickedCard;
  }
  // Otherwise just let them pick the card
  else if (typeof x == "number") {
    let pickedSuit = Math.floor(x / 13);
    return { suit: suits[pickedSuit], card: x % 13 };
  }
}

let myDeck = [
  { suit: "diamonds", card: 2 },
  { suit: "spades", card: 10 },
  { suit: "hearts", card: 4 },
];

let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

Implement swap which receives 2 persons and returns them in the reverse order

export type Person = User | Admin

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

function test1() {
    console.log('test1:');
    const [secondUser, firstAdmin] = swap(admins[0], users[1]);
    logUser(secondUser);
    logAdmin(firstAdmin);
}

function test2() {
    console.log('test2:');
    const [secondAdmin, firstUser] = swap(users[0], admins[1]);
    logAdmin(secondAdmin);
    logUser(firstUser);
}

function test3() {
    console.log('test3:');
    const [secondUser, firstUser] = swap(users[0], users[1]);
    logUser(secondUser);
    logUser(firstUser);
}
Define type PowerUser which should have all fields
from both User and Admin (except for type),
and also have type 'powerUser' without duplicating
all the fields in the code.
type PowerUser = Omit<User, 'type'> & Omit<Admin, 'type'> & { type: 'powerUser' }
The server API format was decided to be the following:

In case of success: { status: 'success', data: RESPONSE_DATA }
In case of error: { status: 'error', error: ERROR_MESSAGE }

The API engineer started creating types for this API and
quickly figured out that the amount of types needed to be
created is too big.
export type ApiResponse<T> = { status: 'success', data: T } | { status: 'error', error: string }

export function requestAdmins(callback: (response: ApiResponse<Admin[]>) => void) {
    callback({
        status: 'success',
        data: admins
    });
}



export function requestUsers(callback: (response: ApiResponse<User[]>) => void) {
    callback({
        status: 'success',
        data: users
    });
}

export function requestCurrentServerTime(callback: (response: ApiResponse<number>) => void) {
    callback({
        status: 'success',
        data: Date.now()
    });
}

export function requestCoffeeMachineQueueLength(callback: (response: ApiResponse<number>) => void) {
    callback({
        status: 'error',
        error: 'Numeric value has exceeded Number.MAX_SAFE_INTEGER.'
    });
}

Clone this wiki locally