The Power of Flexible Types in TypeScript
Tags: typescript, javascript
Published: 2019-10-02 04:00:01

---
title: The Power of Flexible Types in TypeScript
published: true
description: The introduction of TypeScript power that can't put into practice in JavaScript.
tags: typescript, javascript
---

There are so many advantages of TypeScript than JavaScript.

In this article, I will introduce my love-to-use TypeScript use cases.

## Index

- Union Types
- `any` Type
- Overloads are Awesome

## Union Types

### Flexible String Union Types

```ts
// accept all strings
const name: string = 'anyone';

// stricted to string "john"
const john: 'john' = 'someone'; // type error

// union type (only "john" and "michael" are accepted)
const men: 'john' | 'michael' = 'smith'; // type error
```

### Flexible Union Interfaces

```ts
interface OptionA {
  value: 'A';
  funcOnlyOptionA: () => void;
}

interface OptionOthers {
  value: Omit<string, 'A'>;
  funcOptionOthers: () => void;
}
```

`Omit<T, U>` is a TypeScript built-in type and it excludes `U` from `T`.

```ts
// no error
const option1: OptionA | OptionOthers = {
  value: 'A',
  funcOnlyOptionA: () => console.log('Hello Interface A'),
};

// no error
const option2: OptionA | OptionOthers = {
  value: 'B',
  funcOptionOthers: () => console.log('Hello String (without "A")'),
};

// NO ERROR!
// because this is a `union` type
const option3: OptionA | OptionOthers = {
  value: 'A',
  funcOptionOthers: () => console.log('Hello String (without "A")'),
};
```

But, how to compare `option1` and `option2`?

In other words, how to get type errors in the case of `option3`?

Let's get into deeper:

```ts
const isOptionA = (option: any): option is OptionA => {
  return (option as OptionA).value === 'A';
};

const option: OptionA | OptionOthers = generateOption();

if (isOptionA(option)) {
  // now `option` is casted to `OptionA` automatically

  // good
  option.funcOptionA();

  // type error!
  option.funcOptionOthers();
}
```

## `any` Type

### Naturally, Types are Checked Automatically

```ts
interface User {
  name: string;
}

const user: User = { name: 'John' };

const updatedUser: User = {
  ...user,
  name: 'New John', // no errors
  bio: 'I am something!', // type error!
};

// but if we use `any` type, we can't get the benefit above:

const person = { name: 'Alex' }; // `person` is `any` type

const updatedPerson = {
  ...person,
  name: 'AlexxX', // no errors
  bio: 'I am a hacker', // no errors!
};
```

### `any` Type can Convert _ANY_ Types

See a simple example here:

```ts
interface User {
  name: string;
}

const possiblyUserObject: any = {};

const typedUser: User = possiblyUserObject; // no errors

console.log(typedUser.name); // undefined
```

It seems natural enough and there is nothing to study. lol

Anything else? Sure.

### We can Convert JSON Object to the User Defined Types

Consider that we will fetch user data from a server. We may define the http client like this way:

(by using a http library named [ky](https://github.com/sindresorhus/ky))

```ts
import ky from 'ky-universal';

const prefixUrl: string = 'http://localhost';

export class Http {
  public async get(url: string) {
    return ky.get(url, { prefixUrl }).json();
  }

  public async post(url: string, data?: any) {
    return ky.post(url, { prefixUrl, json: data }).json();
  }

  public async put(url: string, data?: any) {
    return ky.put(url, { prefixUrl, json: data }).json();
  }

  public async delete(url: string) {
    return ky.delete(url, { prefixUrl }).json();
  }
}
```

And we can use this `Http` class like below:

```ts
const http = new Http();

const user = await http.get('api/user/1');

console.log(user.name); // no errors, because `user` is `any` type
```

Yes it works perfectly, but we can't get benefit from TypeScript.

So let's rewrite `Http` class with TypeScript generics:

```ts
import ky from 'ky-universal';

const prefixUrl: string = 'http://localhost';

export class Http {
  // because json is `any` type, so we can cast it to the generic type `T`
  public async get<T>(url: string): Promise<T> {
    return ky.get(url, { prefixUrl }).json();
  }

  public async post<T>(url: string, data?: any): Promise<T> {
    return ky.post(url, { prefixUrl, json: data }).json();
  }

  public async put<T>(url: string, data?: any): Promise<T> {
    return ky.put(url, { prefixUrl, json: data }).json();
  }

  public async delete<T>(url: string): Promise<T> {
    return ky.delete(url, { prefixUrl }).json();
  }
}
```

And finally we can get intellisense (code complete, auto correct and so on) from your IDE like editors such as VSCode:

```ts
const http = new Http();

const user = await http.get<User>('api/user/1');
// or:
// const user: User = await http.get('api/user/1'); // no errors!

console.log(user.name); // this user is the type `User`!
```

## Overloads are Awesome

JavaScript can't create overloads as far as I know.

If we fetch user data by `id` or `ids`, the source code is something like this:

```js
// JavaScript

function fetchData(id) {
  if (Array.isArray(id)) {
    return [
      { name: 'john' },
      { name: 'alex' },
    ];
  }
  return { name: 'john' };
}

const data1 = fetchData(1);
console.log(data1.name); // john

const data2 = fetchData([1, 2]);
console.log(data2[0].name); // john

// but, if `id` is object, we must check the return data is array or not:
const data3 = fetchData(idOrIds);
if (Array.isArray(data3)) {
  // data3 is users
} else {
  // data3 is user
}
```

On the other hand, it is possible to create overloads in TypeScript:

```ts
// define overloads!
function fetchData(id: number): User
function fetchData(ids: number[]): User[]

// implementation!
function fetchData(id: number | number[]): User | User[] {
  if (Array.isArray(id)) {
    return [
      { name: 'john' },
      { name: 'alex' },
    ];
  } else {
    return { name: 'john' };
  }
}

const data1 = fetchData(1);
console.log(data1.name); // john (no errors! awesome!)

const data2 = fetchData([1, 2]);
console.log(data2[0].name); // john (no errors! awesome!)
```