Creating Custom Types in Typescript

Johnny Simpson - Mar 9 '22 - - Dev Community

Javascript is a weakly typed language, which means we don't usually think about types. Typescript is strongly typed, which means everything has a type.

Occasionally we want to make an object or the return of a function conform to a certain format. This is where we use custom types. Typescript allows us to define our own custom types, which we can then use in our code.

Why would we use custom types?

Suppose you have a function that always returns data in a certain format, and uses an API to get that data. If the API returns data in the wrong format, we probably don't want the wrongly formatted data to end up in our code where it could cause issues. In such a case, we might ask that the return of a function conforms to a certain type. As such, we would define our own type.

Alias Types

One example of how to create types is called a alias type. An example of how we define a type is shown below:

type Company = {
    name: string,
    address: string,
    value?: number
}
Enter fullscreen mode Exit fullscreen mode

If we give something the type Company, then we expect it to have at least a name and address, and an optional value, which does not have to be given. As you can see, having a question mark in our type denotes that this property is optional.

If we were to use this in code, we might do something like this:

let myFunction = async function(): Promise<Company> {
    let getApi = await fetch('/myApi/data', {
        method: 'GET'
    })
    let getResult:Company = await getApi.json();
    return getResult;
}
Enter fullscreen mode Exit fullscreen mode

In the above code we are returning a Promise of type Company, and if we don't get that, we'll get an error. So for example, if we try to run this and we don't get an address or name back from our API, we will have an error which we can handle.

Extending Alias Types

You can extend alias types, i.e. if you want to add a new element to it. For example:

type Company = {
    name: string,
    address: string,
    value?: number
}

type SubCompany = Company & {
    identity: string
}
Enter fullscreen mode Exit fullscreen mode

In the above, SubCompany will have everything Company has, plus a required attribute called identity.

Using Interfaces instead

Everything we've spoken about so far has been using the type keyword, but we can do the same stuff using the interface keyword instead. It is really personal preference which one you use. Our example above looks like this with interface:

interface Company {
    name: string,
    address: string,
    value?: number
}

interface SubCompany extends interface {
    identity: string
}
Enter fullscreen mode Exit fullscreen mode

Union Types

We can also define custom types using a much simpler syntax known as union types. Let's say we have a type which is either going to be a string or number, called myType. We could define that type as shown below:

type myType = number | string
Enter fullscreen mode Exit fullscreen mode

Literal Types

This is where we set a type that has a specific list of values it can select from. Let's say our original type, Company, can only have three values, red, blue, or green. We can define a literal type, and use that as the type of our name attribute:

type Option = "blue" | "green" | "red" 
type Company = {
    name: Option,
    address: string,
    value?: number
}
Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .