article

Teaching student

Photo by NESA by Makers on Unsplash

JavaScript's New Sexy

TypeScript: The Developer's Tool

If you're new to TypeScript, beware, it's not without its critics. Just two years shy of being 10 years old, it's been around long enough for opinions to have been formed down both sides of the isle. Some champion it, yet others are still not convinced of the weight of its benefits. This article will give a quick overview of three areas in which I believe TypeScript helps engineers at all levels to do their jobs better. And it answers the question as to why the hell anyone would want to add static typing to a dynamically typed language.

Those three areas are: to catch more errors through contracts, less context switching (front-end to back-end), and documentation.

Let's begin…

Catch more errors...

While writing code in any language, we all should take care to follow the contracts in and of the code wether the language that we are writing in forces us to or not. Below is an example of a contract not being honored that will result in undefined being returned when we should have a string.

/**
 * @param {{house: string}} data
 * @returns string
 */
function myFunction(data) {
    return data.house;
}
console.log(myFunction({apple: 'apple'})); // undefined

The former would log undefined since the data object doesn't contain the house attribute. Clearly this is problematic. The dev decided to use the function, for whatever reason, in a way that goes against the documentation. Sure, we are able to use JSDocs to define what a type our data should be and the property it should contain; however, that won't alert us when me make errors as the former.

What if myFunction was tied to your UI and there should always be some text or other data returned by it? Sure, we should write test and code around myFunction to ensure that the data we expect is supposed to be there and in its absence, to fail gracefully. But what about the next person that comes along and wants to use this code? I've seen people mess up simpler things, myself included. Well... that's when TypeScript steps in to help us to quickly spot our blunders. Let's see how.

interface Data {
    house: string;
}
function myFunction(data: Data): string {
    return data.house;
}
console.log(myFunction({apple: 'apple'}));
/*
 * (property) apple: string 
 * Argument of type '{ apple: string; }' is not assignable to
 * parameter of type 'Data'. Object literal may only
 * specify known properties, and 'apple' does not exist in type
 * 'Data'.ts(2345)
*/

In plain old JavaScript we aren't able to enforce contracts (standards of what data should be and how things should be used). But in TypeScript, we have what are called interfaces. These things allow us to define what and how an object should be. Using an interface and assigning it to the type of the data parameter in myFunction, guarantees that the user of myFunction will invoke it with an object of the Data type. And when the user doesn't, TypeScript will alert the user to their mistake. The multiline comment will be the error displayed.

Let's be thankful — for three reasons! It has just saved us from committing and pushing code to a repo that is inherently broken. It's reminiscent of a spell check for code if you want to take it there. It ensures that we are honoring the way the code is intended to work or be used. And finally, we have a reusable interface that we can employ throughout our code. Thank you TypeScript!

Let's consider yet another example using myFunction but this time we will add it to a class.

class Example {
    house: string;
    constructor() {
       this.house = this.myFunction(4);
    }
   
    function myFunction(data: Data): string {
        return data.house;
    }
}
/*
 * Argument of type '4' is not assignable to
 * parameter of type 'Data'.ts(2345)
*/

Depending on your editor, you will see a red squiggly under the 4 in the method call for this.myFunction(4). Again, the multiline comment will be the error displayed.

Front-end, Back-end…the same?

By using the tsconfig.json we can tell TypeScript to compile our code to the version/syntax of JavaScript that we want. This is an amazing tool. Now if we want to context switch with greater ease, we can. For example, take the following:

import getProperty from '../typescript/testing';
interface Data {
    house: string;
}
function myFunction(data: Data): string {
    return data.house;
}
console.log(myFunction({house: 'blue'}));
console.log(getProperty({house: 'green'}, 'house'));
export default myFunction;

This code was written for a Node application. Sure, we could've use Node's experimental modules flag and used a .mjs extension on our file, e.g., node --experimental-modules <filename>.mjs. That's if we are running Node versions 8-12. Or we could use the{ “type”: “module” } in ourpackage.json if we're using versions ≥ 13. But why do that and miss out on all of the other great features of TypeScript? I won't go into them here but there are many. All we need to do is to define how we want our code to be compiled in our tsconfig.json and let TypeScript take the wheel.

Here's a sample tsconfig.json:

{
    "compilerOptions": {
        "moduleResolution": "node",
        "module": "commonjs",
        "target": "ES5",
        "outDir": "lib",
        "rootDir": "/"
    }
}

As a result of running the tsc command in our terminal, we will get the following JS:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const testing_1 = require("../typescript/testing");
function myFunction(data) {
    return data.house;
}
console.log(myFunction({ house: 'blue' }));
console.log(testing_1.default({ house: 'green' }, 'house'));
exports.default = myFunction;

Auto-magic

Furthermore, by using TypeScript, we can write code with the latest language features and not have to concern ourselves with wether or not they will cause support issues¹. Even better!

Documentation

Last but definitely not least is the aspect of documentation. To avoid the endless cluttering of JSDoc comments, we can simply use type declarations, interfaces and such, throughout our code. When used correctly, TypeScript will always give us better-documented code that should cut down on ramp-up time when onboarding new engineers. I can't count the times that I have wished there were well defined contracts in a code base while working on some projects. And the larger the project the more important that becomes.

Conclusion

So hey, there are many other good reasons to use TypeScript, but those listed above are enough to make me chose it as a necessary tool in any JavaScript project. And if that ain't enough to make you consider adopting it…Airbnb uses it.

JavaScript is liberal and fun. It's even more fun when you can quickly ship your code. TypeScript helps us to ship faster. Bottom line is, the extra layer of complexity is more than welcomed to create a better project, and TypeScript is one way to achieve that.

¹This depends on your target and you still may need something like core-js depending on your browser and or runtime — nothing's perfect.

🤷🏿‍♂️

JavaScript
Software Development
Computer Science
TypeScript
Software Engineering
Programming
Technology