Go back to post list

Why TypeScript makes me happy

This Star Trooper makes me happy as the Type Script does, so I used him as my hero image for article. Credits for James Pond

TLDR: TS makes talking about code easier and IDE support gives you better developer experience.


Difficulty: Beginnerย ๐ŸŒฟ

Take a sec to recall a feeling you had when you shipped a product, did some DIY at home or even made some really good coffee.

Feels good, right?

Getting stuff done, building stuff โ€” it feels good. TypeScript helps me build things faster and improves my experience during the process. I hope it can make you happy too, but I won't guarantee it.

What does TypeScript give me, that's missing in JavaScript?

Types? ๐Ÿค”

I feel there's a common misconception that TypeScript "adds types" to JavaScript.

Uncaught TypeError: undefined is not a function

If you recognize the quote above, you know JavaScript already has types.
We can check typeof and instanceof, declare new classes. JavaScript certainly has types.

TypeScript gives you more language to talk about them, static type checking to tell you about errors faster, better editor code completion and refactorings[1] to sweeten the deal.

Let's start with code completion and move to talking about types later.

Editor support

We all know software can get pretty complex and it can be tremendously hard to become productive in new codebase. If only something could help you make sense out of this mess... Fortunately, the code is not just a text. It's parsed into an Abstract Syntax Tree and compiled[2].
And your editor (maybe with help of some plugins) understands it.

Many text editors can support you even if you're not using statically typed language.
Code completion in the example below works both with TypeScript and JavaScript.

If you try this in dev tools, the browser will help you only at the beginning with the ".createElement" part. Go to CodeSandbox

It gets worse in JavaScript when we introduce more complexity. For example, let's assume we want to console.log every time we create an element.

Go to CodeSandbox

I know createElementAndLog given "script" will return HTMLScriptElement and I know it has src property but I can't really tell this to my text editor in JavaScript.

However, when I'm using TypeScript, thanks to lib.dom.d.ts, my text editor can win every Web API knowledge quiz. I can stop looking up property names on MDN.

HTMLElementTagNameMap is defined here, but we don't have to know it. Go to CodeSandbox

I didn't make up these types. I followed createElement to its type declarations and just did the same thing, because I want my function to take exactly the same parameters as createElement.

Notice the red underline. If I ignore the suggestion and leave it like that, TypeScript will tell me. "Property 'sr' does not exist on type 'HTMLScriptElement'. ts(2339)". And it's true.

DOM APIs and libraries are documented in types. I don't have to keep the docs open to use them.
My text editor will tell me everything I need to know when I need it.

We should strive to make the code as self-documenting as possible.
Writing types and static type checking help me do that.

Talking about types

Why do I want more language to talk about types?
Because my project managers and clients talk to me about Customers, Emails and Prices and don't really care about strings or numbers. I get it. You can't really add Dollars to Euro, right?

Example time. Imagine we're building a game. Games are fun examples, and they're not really more complicated than business apps.

I'm gonna use product types, sum types and nominal typingย to model the problem.

Almost realistic game description

*Okay dude, so each character has a level and based on this level and a die roll we tell who won the fight. Of course after you log in you can choose one of your characters by name. We begin with a limit of 2 characters, but increase the limit up to 4 for a price of 100 dragon coins or 1 trillion (10*12) gold coins per slot. A character can have a profession (but he doesn't have to) and the professions we have are Wizard, Warrior and Ranger.
~ the game designer ๐Ÿ‘จโ€๐ŸŽค

I'll write types first because:

  • I want to formalize the problem description so I don't forget what I already know
  • I'd like to make sure static type checking will keep me from doing stupid bugs
  • I can't implement game logic yet, because my game designer alter ego didn't tell me how does this fight mechanic really work ๐Ÿ˜‰

Let's start with characters. We know every characters name and level.

type Character = {
  name: string;
  level: number;
};

We need a string and a number to create a Character. That's often called a product type.

You could have noticed I've left out the profession. But how do we type it?
We could go with profession: string but this doesn't represent what we know about the problem already. "Software Engineer" would be a legal profession and we don't want this. What would he do against a Wizard? Bore him with a talk about clean code and architectural patterns?

That's what sum types are for. You can think of types as of sets of possible values a variable can have. Now we need a set of three literal types, representing our character professions.
We can use a union of string literals or enum[3].
I'll go with enum for convenience, but either one is fine.

enum Profession {
  Wizard,
  Warrior,
  Ranger,
}

type Character = {
  name: string;
  level: number;
  profession?: Profession;
};

And now we can define the Player

type Player = {
  displayName: string;
  email: string;
  characters: Character[];
  dragonCoins: number;
  gold: number;
};

But wait! The game designer said there's a limit on number of characters. We'd like to make illegal states unrepresentable. A tuple looks like a fine choice.
We can try representing empty slot as undefined.

type Characters = [Character?, Character?, Character?, Character?];

// โœ… Works as expected.
const _1: Characters = [undefined, undefined, undefined, undefined];

// ๐Ÿ”ฅ Doesn't compile -- as expected, there are too many elements.
const _2: Characters = [undefined, undefined, undefined, undefined, undefined];

// โŒ This works, but should not.
const _3: Characters = [undefined];

But we quickly find out, that it doesn't allow us to enforce minimum number of elements.

const EmptySlot = null;
type EmptySlot = typeof EmptySlot;

type CharacterSlot = Character | EmptySlot;
type Characters =
  | [CharacterSlot, CharacterSlot]
  | [CharacterSlot, CharacterSlot, CharacterSlot]
  | [CharacterSlot, CharacterSlot, CharacterSlot, CharacterSlot];

This is pretty verbose, but does the job. An undefined anywhere here will cause type error.

// โœ…
const good: Characters[] = [
  [EmptySlot, EmptySlot],
  [EmptySlot, EmptySlot, EmptySlot],
  [EmptySlot, EmptySlot, EmptySlot, EmptySlot]
];

// ๐Ÿ”ฅ Doesn't compile as expected.
const _2: Characters = [EmptySlot, EmptySlot, EmptySlot, EmptySlot, EmptySlot];

// ๐Ÿ”ฅ Doesn't compile as expected.
const _3: Characters = [EmptySlot];

When we hover Charactersย the tooltip is pretty ugly though

type Characters = [CharacterSlot, CharacterSlot] | [CharacterSlot, CharacterSlot, CharacterSlot] | [CharacterSlot, CharacterSlot, CharacterSlot, CharacterSlot]

๐Ÿ˜จ

type CharacterSlotsSizes = 2 | 3 | 4;

 // `interface` doesn't expand in error messages and logs
 interface CharacterSlots<K extends CharacterSlotsSizes>
   extends Array<CharacterSlot> {
   length: K;
 }

 type Characters =
   | CharacterSlots<2>
   | CharacterSlots<3>
   | CharacterSlots<4>;

And now we'll see the message below in all of the errors and hover hints.

type Characters = CharacterSlots | CharacterSlots | CharacterSlots

Much better.

I'll change the Array above to ReadonlyArray as a finishing touch.

DragonCoins โ‰  Gold

We're almost done. I'd like to differentiate the "gold" and the "dragon coins" in the type system.
Accidentally assigning gold to player.dragonCoins would be unfortunate, because we're expecting to have bags of gold ๐Ÿ’ฐ and just a little of dragon coins โ€” they're our paid currency, bought for real money, so we're going to be super careful around them.
We can do this with nominal typing.

type DragonCoins = number & { __type: "DragonCoins" };

// This function transpiles to identity,
// so tools like Closure Compiler can get rid of it[4].
function DragonCoins(coins: number) {
  return coins as DragonCoins;
}

type Gold = number & { __type?: "Gold" };

function Gold(gold: number) {
  return gold as Gold;
}

// Implicit conversion from numbers to Gold should work.
// โœ…
const _g1: Gold = 1000;

type NotGold = number & { __type?: "NotGold" };
// ๐Ÿ”ฅ "Type 'Gold' is not assignable to type 'NotGold'"
const _g2: NotGold = Gold(1000);

// Implicit conversion from numbers to DragonCoins shouldn't work.
// ๐Ÿ”ฅ "Type 'number' is not assignable to type 'DragonCoins'"
const _d1: DragonCoins = 10 ** 12;

// โœ…
const _d2: DragonCoins = DragonCoins(100);

// We can still treat DragonCoins and Gold as numbers
// โœ…
const _g3 = Gold(10000).toLocaleString();

// โœ…
const _d3 = DragonCoins(100).toFixed(2);

TypeScript will keep us from mixing up gold and dragon coins and since these functions are just identity after transpilation, they can be thrown out by Closure Compiler or UglifyJS in production. We pay no runtime cost for the type safety.

How does the final Player type look like?

type Player = {
  displayName: string;
  email: string;
  characters: Characters;
  dragonCoins: DragonCoins;
  gold: Gold;
};

If we want to go further we can add type Email for all emails that passed the regex, but I think we're fine for now. We should always prioritize delivering value before the deadline over solving the puzzle and a getting perfect type for everything.


[1] Yup. TypeScript provides refactorings. They don't have to be implemented in all of the editors one by one.
[2] There are more steps here in many cases.
[3] And const enum. These values will be inlined. This is a similar solution to defining constants in webpack DefinePlugin. They're not supported by Babel though.
[4] link to closure compiler playground
(๐ŸŒฟ) I'm mostly using type aliases to make things easier for people new to programming. Differences between a type alias and an interface.