TypeScript Basics: Custom Types

My journey to learn TypeScript

ยท

7 min read

TypeScript Basics: Custom Types

Hello there ๐Ÿ‘‹ I am Eslam Ahmed SW engineer and frontend passionate, Welcome to the third article in this series about Typescript. I am very glad to see new readers here
You can check the previous articles:

  1. TypeScript: A start
  2. TypeScript Basics: Types

In the last article we have talk about:

  1. Why to use types
  2. Basic types
  3. How to add types to functions
  4. Some utility types and never - void - any types
  5. Literal types
  6. Object types
  7. finally, we wrote our first algorithm (Binary Search) in Typescript

It was very rich article make sure you check it if you didn't yet.
Today We are going to know more about types but this time about custom types and here is our index:

  1. What are custom types and Why to use them.
  2. Defining custom types with keyword type ๐Ÿ‘†
  3. Defining custom types with keyword class โœŒ๏ธ
  4. Defining custom types with keyword interface ๐Ÿ‘Œ
  5. When to use each of them?
  6. Union of Custom Types ๐Ÿค
  7. Unknown โ”

Custom Types

TypeScript is a superset of JavaScript that gives you the ability of using all the features of JavaScript in runtime and adds a type checks at compile time, That type checking prevent lots of bug.
Custom Types gives you the ability of creating defining types from other types whether basic types or other custom types. now let's see how to define those types

Defining Types Using type Keyword

The type keyword allows you to declare a new type or a type alias for an existing type. Ypu have already seen the simple using of type in last article whe we defined an object, Now let's see more concise example. Suppose we are coding a geometry app and we want to represent points, rectangles and circles in 2d grid.

  • Point: described with x, y values
  • Rectangle : defined with the bottom left and top right points
  • Circle: defined with the center point and the radius
Type definition
//define the point type 
type Point = {
    x : number,
    y : number
}

// define rectangle type using put point type
type Rectangle= {
    bottomLeft: Point,
    topRight: Point
}

// define circle type
type Circle = {
    center: Point,
    raduis: number
}
Usage
const originPoint : Point = {
    x : 0,
    y : 0
}
let p1 : Point = {x:1 ,y:1}
let rect1 : Rectangle = {
    topRight : p1,
    bottomLeft : originPoint 
}

As always If we forgot to add an property or used wrong type an error will be shown, Here I intentionally forget to add the radius while declaring circle

Error in using type

You can although make some properties optional using ? operator, the following example assumes we are defining user type to hold date retrieved from database, each user has name, id and optional image url

type User = {
    id: number,
    name: string,
    imgURL?: string 
}

as you can see now errors image.png

Also functions could have type aliases, going back to our geometry app example and add function to draw a point

const drawPoint = (p : Point) => {
    //do some magic to draw point
    if (magicWorks) {
        return true;
    }
    return false;
}

type PointDrawer = (p : Point) => boolean

const drawLine= (slope:number, drawer:PointDrawer) => {
    //do some magic to draw line using y=mx
}

More about type key word and type aliases will be mentioned eventually but let's move to next jey word that is used for defining types, make some coffee โ˜•๏ธ and join me.

Defining Types Using class Keyword

Here you can create an ES6 class to define the type, If you are new to the concept of oop and ES6 classes you could check Omotola's article or Faheem's article. In Javascript ES6 we create new class as follow

    "use strict";
    class Person {
        constructor(firstName, lastName, age) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.age = age;
        };
    }
    const lang = new Person("Java", "Script", 26);

In TypeScript you don't need to explicitly write the constructor body, the compile will do it instead of us "Thanks mr tsc"

class Person {
    constructor(public firstName: string, 
                public lastName: string, public age: number) {};
}
const lang = new Person("Type", "Script", 8);

More about classes and oop paradigm are and how to use them in TypeScript in the next set of articles.

Defining Types Using interface Keyword

Interface is a structure that defines the contract in your application. It defines the syntax for classes to follow. Classes that are derived from an interface must follow the structure provided by their interface.

In English you only type the properties and function signature, function implementation is written in the inherited classes.

Many Programming language such as Java and PHP. JavaScript didn't supported it yet however Typescript implements it and you can use it, just wait for the next few articles. now we are going to use it only in defining custom types.

interface Person {
    firstName:string,
    lastName:string,
    age:number
}

const p: Person =  {
    firstName: "Osama",
    lastName: "Ahmed",
    age:23
}

When to use each of them?

The following image shows each one of the three key words type - class - interface on the left and the JavaScript code the compiled to on the right try to play with the code here . image.png

here is three tips for selecting the proper keyword in type definition

  • If you only need to declare types for type checking use type or interface
  • If you want this type to represent a value in runtime use class
  • types defined type keyword support intersection and unions between them.

Union of custom types.

We have already talk about type unions in the previous article. as a refresh type union enables you to define variable that can take to types such as number or string

let username: string|number;

Custom types union is almost the same, here is a great example from Typescript Quickly

interface Rectangle {
  kind: "rectangle"; 
  width: number;
  height: number;
}
interface Circle {
  kind: "circle";
  radius: number;
}
type Shape = Rectangle | Circle;

function area(shape: Shape): number {
  switch (shape.kind) {
    case "rectangle": return shape.height * shape.width;
    case "circle": return Math.PI * shape.radius ** 2;
  }
}
// note x ** 2 <=> x power 2 <=> x * x

Another way to check the actual type is using in keyword. in: checks if specific property exists in type. the next code snippet is a refactoring to the previous area function using in keyword, it may be not the optimal implementation but it fulfills the purpose

function area(shape: Shape): number {
  //check if the shape has the property radius 
  if ("radius" in shape){
    return Math.PI * shape.radius ** 2;
  }
  // if not so it's rectangle
  return shape.height * shape.width;
}

unknown Type in TypeScript

While using TypeScript you tend to annotate every variable with it's type or possible types using unions but sometimes you don't know the type or you just migrated large code from JavaScript into TypeScript and want to run this code without refactoring and annotating every variable a solution would be using any type whether implicitly var x or explicitly x : any= "delightful". However any must be your last resort as it declines all type checking and brings back all JavaScript downsides. unknown is very similar to any but it's more safe to be used. let's see some examples to clarify that 1- you can assign any type to both of them

// It is ok to change the type for both
let varAny:any = "ANY"
varAny = 5
let varUnknown: unknown = "UNKNOWN"
varUnknown = 5

2- variable of type any could be assigned to annotated variable (string for example)

let varStr : string 
let varNum:number

varStr  = varAny 
/*
Typescript is being optimistic that varAny will be string at runtime 
which is wrong as varAny is number but no compilation error is shown
*/

3- variable of type uknnown could not be assigned to annotated variable image.png

4- you can access imaginary members or function of variable of type any and no errors will be detected until runtime

varAny.isTrue.lst.forEach((x:number):number => x**2)

5- It's clear now how bad any is and how unknown is safer, but how to use unknown?

  • to use variable of type unknown you need to make type checking | narrowing first image.png

That's it for today I hope you enjoyed this article and learnend new things if so don't forget to like, comment and share. Constructive Feedbacks are very welcomed even in commints section or messages on twitter, cheers ๐Ÿ‘‹

ย