Photo by NESA by Makers on Unsplash
TypeScript: The Developer's Tool
5 min read
Aug 30, 2020
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…
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.
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!
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.
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.
🤷🏿♂️