The Typescript Book

Notes on how to use the Typescript language

How to create a frontend development environment for Typescript

from node:alpine
workdir /usr/application
cmd ["npm", "start"]
copy package.json .
run npm install
copy . .

How to create a backend development environment for Typescript

FROM node:alpine
WORKDIR /usr/server
CMD ["npm", "run", "development"]
COPY package.json .
RUN npm install
COPY . .

Three cases where you need explicit type annotation

As a rule you should rely on type inference. In 3 specific cases you need to use explicit type annotation.

// When function that returns the "any" type e.g. JSON.parse()
// interface Point {
//   x: number
//   y: number
// }
// const thePoint: Point = { x: 10, y: 20 }
const thePoint = { x: 10, y: 20 }
const coordinates = JSON.parse(JSON.stringify(thePoint))
// Solution#1 const coordinates = JSON.parse(JSON.stringify(thePoint)) as Point
// Solution#2 const coordinates: Point = JSON.parse(JSON.stringify(thePoint))
console.log(coordinates)

How to annotate a function

// Case 1: Annotate variable
const sum
  : (a: number, b: number) => number 
  = (a, b) => a + b

How to annotate an object

const person 
  : { name: string, age: number, 
      coordinates: { longitude: number, latitude: number }}
  = { name: 'bob', age: 35,
    coordinates: { longitude: 1.23456, latitude: 6.54321 }}

How to annotate a destructured value

const todaysWeather = {
  date: new Date(),
  weather: 'sunny',
}

// Case 1: parameter is not destructured
const logWeather1 = (forecast: { date: Date; weather: string }): void => {
  console.log(forecast.date)
  console.log(forecast.weather)
}

logWeather1(todaysWeather)

How to annotate arrays

// homogenous arrays
const array: string[] = []
array.push('')
const value = array.pop()    // value inferred to be of type string

How to annotate tuples

// A tuple is an array that expects specific types at specific indexes
// The code below shows how to use an enumeration to enforce this ordering

// define enumeration to provide access to tuple elements at specific indicies
enum Quality { color, fizzy, calories }
const { color, fizzy, calories } = Quality

// define the type of the tuple
type Drink = [string, boolean, number]

// define an instance of a tuple
const pepsi: Drink = ['brown', true, 40]

pepsi[color] = 40        // error - cannot assign number to string 
pepsi[fizzy] = 'brown'   // error - cannot assign string to boolean 
pepsi[calories] = true   // error - cannot assign boolean to number 

How to create types in Typescript

// (1) create a type for a function
type predicate = (value: number) => boolean
const isEven: predicate = (value) => value % 2 === 0

// (2) create a type for an object
type Person = { name: string; age: number }
const robert: Person = { name: 'bob', age: 35 }

// (3) create a type for a tuple
type Beverage = [string, boolean, number]
const drPepper: Beverage = ['brown', true, 75]

// (4) create a stype for a heterogenous array
type Dates = (string | Date)[]
const rolodex: Dates = ['2020-09-25 10:00:05', new Date()]

How to create interfaces in Typescript

interface Reportable {
  name: string
  year: number
  broken: boolean
  print(): void
}

const car = {
  name: 'civic',
  year: 2000,
  broken: true,
  print() {
    console.log(`Name: ${this.name}`)
    console.log(`Year: ${this.year}`)
    console.log(`Broken: ${this.broken}`)
  },
}

const printVehicle = (vehicle: Vehicle) => {
  vehicle.print()
}

printVehicle(car)

How to add method and property accessibility specifiers

class Vehicle {
  // only callable within class and subclasses
  protected drive() {
    console.log('chugga-chugga!')
  }
  // callable by anyone on class instance or subclass instance
  public honk() {
    console.log('beep-beep!')
  }
  // callable only within class 
  private price() {
    console.log('$1200')
  }
}

class Car extends Vehicle {
  // visibilty of protected method can be increased when overriding
  public drive() {
    console.log('vroom-vroom!')
  }
}

const car = new Car()
console.log(car.drive())
console.log(car.honk())

How to use generics with constraints

class Printer<T> {
  constructor(private valueOne: T) {}

  print(valueTwo: T): T {
    console.log(valueTwo)
    return this.valueOne
  }
}

const printer = new Printer<number>(42)
printer.print(24)
// printer.print('invalid value')

How to use decorators

class Boat {
  @log('I', 'could')
  pilot(): void {
    throw new Error('not pilot boat')
  }
}

function log(a: string, b: string) {
  return function (target: any, key: string, descriptor: PropertyDescriptor) {
    const method = descriptor.value
    descriptor.value = function () {
      try {
        method()
      } catch (error) {
        console.log(`${a} ${b} ${error.message}`)
      }
    }
  }
}

const boat = new Boat()
boat.pilot()              // prints the message "I could not pilot boat"

How to use React prop types with Typescript

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

// (1) These are where you define the prop types for your component
interface AppProps {
  // (2) Using JSDOC comments show up in Visual Code on hover 🥰
  /** The text to show */
  text?: string
}

class App extends Component<AppProps> {

  // (3) These are your default prop values 
  static defaultProps = {
    text: 'Not Available',
  }

  render() {
    return <h1>{this.props.text}</h1>
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

How to use useRef instead of useState to represent a form

The usual approach for a form is to use state and set up change handlers on each input field. However this results on change handlers being fired on every keypress, potentially a large number of events. Since we are only interested in the contents of the input fields on a form submission, we can use refs to the input fields and only grab their values once when there is a form submission.

import {
  FormEvent,
  FormEventHandler,
  FunctionComponent,
  useRef,
  RefObject,
} from 'react'

const getAndReset = (ref: RefObject<HTMLInputElement>): string => {
  if (!ref.current) return ''
  const value = ref.current.value
  ref.current.value = ''
  return value
}

export const SignIn: FunctionComponent = (): JSX.Element => {
  const emailRef = useRef<HTMLInputElement>(null)
  const passwordRef = useRef<HTMLInputElement>(null)

  const handleSubmit: FormEventHandler = (
    event: FormEvent<HTMLFormElement>
  ): void => {
    event.preventDefault()
    const email = getAndReset(emailRef)
    const password = getAndReset(passwordRef)
    console.log('form submission', email, password)
  }

  return (
    <div className="sign-in">
      <h2>I already have an account</h2>
      <span>Sign in with your email and password</span>
      <form onSubmit={handleSubmit}>
        <input name="email" type="email" required ref={emailRef} />
        <label htmlFor="email">Email</label>
        <input type="password" name="password" ref={passwordRef} />
        <label htmlFor="password">Password</label>
        <input name="submit" type="submit" value="Submit Form" />
      </form>
    </div>
  )
}

Last updated