TypeScript

Why I prefer TypeScript

Opinionated view about why I use TypeScript

Disclaimer: This article is biased and represents my opinion.

Arguments against TypeScript

I hear a lot of arguments against TypeScript. I think that although it has a learning curve, and it sometimes makes you write more code, those drawbacks are minor, and writing TypeScript correctly can eliminate them.

Learning Curve

TypeScript has a learning curve (meaning that you have to learn new things besides just JavaScript), but if you already know JavaScript - it’s easy to learn. Why? because TypeScript is just JavaScript with types. JavaScript code is valid TypeScript code. There is no major syntax change. Actually you can learn most of TypeScript under 10 minutes.

TypeScript is just JavaScript with types

Depending on the ecosystem or libraries you are using, you will have to learn how to use their specific types. Usually, you can find examples in the docs of those frameworks/libraries. For example in React you may want to write:

const inputRef = React.useRef<HTMLInputElement | null>(null);
const [name, setName] = React.useState<string | null>(null);
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
  setName(e.target.value);
}

For learning react specific types I recommend react-typescript-cheatsheet. After you get used to it - this task becomes easy.

More Code

I recommend writing your TypeScript code like JavaScript, and only add types if TypeScript does not infer them automatically. For example, Instead of writing:

const firstName: string = 'Nir';

I prefer to write:

const firstName = 'Nir';

Not only the transpiled JavaScript code will be the same, but also TypeScript will do better job specifying the type automatic to stricter type (the exact string “Nir” known as string literal).

TypeScript is smart enough to infer types automatically

TypeScript will make you write more code, but it depends on your project. The more types you have to write, the more you can benefit from TypeScript (if TypeScript force you to write the type, you will get autocompletion for it).

3rd party libraries

Our modern IDEs use TypeScript under the hood to help you with auto-completion. Most of the types of the popular projects are already shipping with TypeScript support out of the box (libraries written in TypeScript or added some *.d.ts file), and if not - there is a big chance that the community added types to them in DefinitelyTyped, meaning that and you add them to your project with npm install @types/library-name --save-dev. With typings to those libraries, you can get free auto-complete, and it’s a good place to explore the library and understand it better.

I don’t need TypeScript because I use WebStorm / VSCode / other modern IDE

WebStorm / VSCode does provide autocompletion sometimes. But under the hood they use TypeScript. The difference is that there are some cases that TypeScript does not infer the types automatically if you won’t specify them (for example for function parameters) - and then you won’t get an autocompletion. Those are the cases that I write the types by myself. So the IDE auto-completion is limited.

PropTypes is good enough for type checking

Really? So how do you specify function parameter types? or return types? What about HOC (a function that returns a function)? TypeScript is stricter than PropTypes and can specify types that propTypes can’t.

TypeScript check types in compile time.

TypeScript check types in compile-time - PropTypes check types in runtime. This helps TypeScript developers to have a shorter feedback loop, and get type indication as they write the code. On the contrary, to get PropTypes type error, you will have to run your program in development mode, and reproduce the specific case where the types mismatching. You may run your program but not call the component, and if you have a component that calls several times with different parameters you may miss some calls with the wrong types.

In case of type mismatch - PropTypes will print you a small console.log warning, while TypeScript will show you the exact place in your IDE.

Also the TypeScript syntax is clearer. instead of:

import React from 'react';
import PropTypes from 'prop-types';

function MyComponent(props) {
  return <div>{JSON.stringify(props)}</div>;
}

MyComponent.propTypes = {
  objProp: PropTypes.exact({
    name: PropTypes.string.isRequired,
    quantity: PropTypes.number.isRequired,
  }).isRequired,
  optionalEnum: PropTypes.oneOf(['News', 'Photos']).isRequired,
};

I prefer:

import React from 'react';
import PropTypes from 'prop-types';

interface IProps {
  objProp: {
    name: string;
    quantity: number;
  };
  optionalEnum: 'News' | 'Photos';
}

function MyComponent(props: IProps) {
  return <div>{JSON.stringify(props)}</div>;
}

In complex objects types - like your global store or your API response - you will have to specify PropTypes with a lot of isRequire but it’s less elegant than TypeScript.

If I want to know what is the type - I can dive into the source code

You can do it, but it’s hard. You have to remember the context of the function and the flow to detect the type - like debugging. Doing so will make you switch context every time you want to reuse a function/variable in your system and seek the code flow only to understand how to use it properly. If your code is covered with tests - it makes it easier to understand. But isn’t it better to have a tool that does it for you, automatically?

I have already written my code in JavaScript

If you want to adopt TypeScript into your project - you don’t have to rewrite all your code to TypeScript. You can have a gradual migration. First, use a less strict tsconfig (allow JS), change your file extension to .ts / .tsx. New features will be written in TypeScript, and you can gradually add types to the old files until you can make the stricter tsconfig. Also, you can use automation to help you - see typewiz. For more details read this article. Note: it’s common to find bugs in your code errors when migrating JS to TS, so you can benefit from the migration.

Always bet on JavaScript

I like this sentence. But it may be misunderstood. Always bet on JavaScript refer to JavaScript compared to Java, Python, C, C++, Go… and TypeScript is just JavaScript.

It may be rotted in the future (like CoffeeScript). If this will be the case I know that I can transpile it back to readable ESNext (only without the types) and start my refactoring from there. But for me - TypeScript is like prettier or ESLint. It’s a tool for developer productivity. You don’t want to do manual tasks repeatedly (understanding the types) so you use a tool for it. And once you have it - you never want to go back.

It’s annoying to specify types

As I mentioned before - you don’t have to specify types for EVERYTHING. If you find specifying types hard, I bet you are using TypeScript for 3rd party library for the first time. Like any 3rd party library - you have to read the documentation or see examples to use it. BTW - if you specify types for it, you will have an easier life in the future using this library.

TypeScript is slow

This can be a true concern about TypeScript. In large projects, TypeScript type checking can take time. It does not have an impact on production, but for developer experience. Although some configurations help with performance (isolated modules, incremental), and the TypeScript team tries to optimize it with every release, this issue is not 100% solved and it’s a real concern. But still, this is a minor issue compared to the TypeScript advantages. I haven’t noticed slow type checking yet, but it may happen to you depending on the project size.

Arguments for TypeScript

Now the real fun begins. The most exciting features of TypeScript are types. You get an auto-completion, understand your code better, and can detect errors early.

Auto-Completing

Because TypeScript understands your code and types - you will get a free autocompletion. Accessing object properties? CMD / CTRL + SPACE. This improves the developer experience a lot.

Refactoring

You can extract methods and variables easily and you don’t have to read the whole code just to understand that your current refactoring is in the right direction. You don’t have to struggle just to write your code, you just need to think about its structure.

Error finding

If your types reflect your system well - the TypeScript compiler will warn you in the IDE in case it detects type mismatch. It may suggest an automatic quick fix for the problem. This way, you don’t have to debug the code yourself just to understand that you did something wrong - you get an immediate error in your IDE.

Strict options like null check

With TypeScript, you get free null checking. For example:

const message = Math.random() > 0.5 ? 'Message' : null;
const shortMessage = message.substring(5);

you will get error Object is possibly 'null'.ts(2531) because message used as not null. So you can fix it:

const message = Math.random() > 0.5 ? 'Message' : null;
const shortMessage = message == null ? null : message.substring(5);

and have a better code without this bug. You could prevent this bug without TypeScript, but with TypeScript, you get another safety net. No more Uncaught TypeError: Cannot read property 'myProperty' of undefined. Programmers make mistakes - but automatic TypeScript can prevent those mistakes.

Set Boundaries

TypeScript has interfaces. And interfaces can be used as boundaries in Object-Oriented Programming. You can create better software depending on this abstraction instead of the concrete implementation.

TypeScript provides documentation

By specifying types, you add constraints about your API. Comments can lie - code does not. The documentation/code examples can miss some parameters from a function - but the types code can’t. Also, using types formalize the usage of the function - better than any documentation.

More Reliable Code

This comes from the article All js libraries should be authored in TypeScript. Types are there whether you like it or not. APIs never accept “whatever” and output “whatever”. There are always preconditions and postconditions. Good library APIs have good contracts.

Good library APIs have good contracts.

You always assume the parameter type inside your head. Using TypeScript and specify types promise you that the user of the function commits to the contract. So you don’t have to deal with users that call your function with string although it accepts only numbers. You don’t have to check the type of their parameters and throw an error. You rely on the types.

Types provide context

Consider this code

const admin = await getUserFromServer({ role: 'admin' });

What is admin? is it his name string? is it an object? whats does this object contains? is it nullable? Usually when you see code like that - you go over the getUserFromServer implementation and read the code carefully just to understand that you get Promise<{id: string, name: string, role: "admin" | "user"} | null>. Using TypeScript will help you with that even without reading getUserFromServer code. With TypeScript and good IDE integration, you will get the type of the user without the need to read through the function.

This point is more relevant to complex data objects. Consider this code

const data = await getData();

const { trees, birds } = data;

const forest = trees
  .map(t => {
    return { ...t, bird: birds.find(b => b.parentTreeId === t.id), isTall: t.height > 120 };
  })
  .filter(t => {
    return (
      (t.location.lat + MARGIN > currentLat || t.location.lat - MARGIN < currentLat) &&
      (t.location.lng + MARGIN > currentLng || t.location.lng - MARGIN < currentLng)
    );
  });

const forest = trees
  .map(t => {
    return { ...t, bird: birds.find(b => b.parentTreeId === t.id), isTall: t.height > 120 };
  })
  .filter(t => {
    return (
      (t.location.lat + MARGIN > currentLat || t.location.lat - MARGIN < currentLat) &&
      (t.location.lng + MARGIN > currentLng || t.location.lng - MARGIN < currentLng)
    );
  });

forest type inferred automatically by TypeScript (depending on getDate return type). So you don’t need to calculate the data transformation yourself. And if I would write more efficient code with reduce instead of map and filter combination, that would be harder to understand.

You can learn and understand the code better by looking at the types

For example (taken from react type definitions):

function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
...
type Dispatch<A> = (value: A) => void;
type SetStateAction<S> = S | ((prevState: S) => S);

You can see that initialState can be the state itself or a function that returns the updated state. And it returns a tuple: the first element is the state. The second element is a function with a parameter that is the new state/update function that accepts the prevState as a parameter and returns the new state. So now you know what options are available for you to call this function.

Standard Superset of JavaScript

There are other supersets of JavaScript like flow. TypeScript does the same job (with some syntax differences/features). TypeScript is more popular than those languages by far, has better tooling, ecosystem, and library support. By the way - I see more and more open source projects that migrate from flow to TypeScript (including in my past job) like Vue.js. TypeScript is not going to die (not for now), because JavaScript is not going to die (who said “Always bet on JavaScript”). If you don’t use TypeScript specific features (like annotations and enums) you will be ok in any given time to move.

Shorter your feedback loop

You get an immediate response and feedback from TypeScript for types and errors. You don’t have to run your code. If you have a mistake in your code types you will have an immediate error.

Community and well supported

Almost every notable project is either:

  • Written in TypeScript
  • Shipping with TypeScript typings
  • Has TypeScript types in @DefenetlyTyped

Moreover, the TypeScript ecosystem contains:

  • Vue (migrated their source code from flow to TypeScript)
  • Angular
  • Nestjs

Easier to get into the code

Especially for new contributors that do not understand your code. You feel safer with TypeScript. If you get an auto-completion for properties, you can trust it. You don’t have to find in your code where does this function is called to get its input type. And if you call it in the wrong way - you will get immediate feedback.

Subscribe to Nir Tamir

Get the latest posts delivered right to your inbox