DEV Community

Omer Elbaz
Omer Elbaz

Posted on

Why you should learn TypeScript today

1. TypeScript makes code easier to read and understand.

When you use TypeScript, you can see the types of variables and functions as you're writing your code. This helps you to understand what your code is doing and makes it easier to find bugs. type annotations also make it easier for other developers to understand your code.

2. TypeScript can help you catch bugs early.

Because TypeScript is a typed language, the TypeScript compiler can catch errors in your code before you even run it. This means that you can find and fix bugs more quickly, and your code will be more reliable.

3. TypeScript is easy to learn for existing JavaScript developers.

If you know JavaScript, then you already know the syntax of TypeScript. You can start using TypeScript immediately, without learning a new language. And because TypeScript is a superset of JavaScript, any existing JavaScript code will work with TypeScript without any changes.

4. The TypeScript compiler is available for free.

The TypeScript compiler is open source and available for free from Microsoft. You can use the compiler in any project, without paying any license fees. And because the compiler is written in TypeScript, it's easy to extend and customize for your own needs.

  • TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
  • TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
  • Any browser. Any host. Any OS. Open source.

Background

JavaScript is a dynamic language with a loosely typed system. This can lead to some issues as your code scales in size and complexity. TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. This means that you can use TypeScript with any existing JavaScript code and be confident that it will run correctly. TypeScript also offers strong typing and other features that can make your code more robust and easier to maintain.
In this article, we'll take a look at some of the key features of TypeScript and how they can help you write better code.

Types

One of the most important features of TypeScript is the ability to define types for your variables, functions, and classes. By using types, you can add an extra level of safety to your code and make sure that you're always working with the correct data type.
For example, let's say you have a function that takes an array of numbers and returns the sum of all the numbers in the array. With TypeScript, you could define the type of the input parameter as follows:

function sum(numbers: number[]) {  }
Enter fullscreen mode Exit fullscreen mode

This way, if you try to pass anything other than an array of numbers into the function, you'll get an error from the TypeScript compiler. This can help you catch bugs early on and make sure your code is always running correctly.

Classes

In addition to variables and functions, you can also use types with classes in TypeScript. For example, here's a simple class definition:

class Point {  }
Enter fullscreen mode Exit fullscreen mode

You can then create instances of this class and specify their types:

let point1: Point = new Point();
Enter fullscreen mode Exit fullscreen mode

By adding types to your classes, you can again catch bugs early on and ensure that your code is always running correctly.

Interfaces

In addition to classes, TypeScript also has interfaces which are used to define contracts within your codebase. Interfaces allow you to specify what methods and properties a class must have in order for it to be considered as implementing the interface.
For example, let's say you have an interface called Shape with a method called area(). You could then create a class called Square that implements this interface:

class Square implements Shape {  }
Enter fullscreen mode Exit fullscreen mode

If you try to create a Square instance but forget to implement the area() method, you'll get an error from the TypeScript compiler letting you know that something is wrong.
This helps you avoid accidentally introducing bugs into your code by forgetting to implement required methods or properties

TypeScript adds optional types, classes, and modules to JavaScript.

TypeScript is designed for the development of large applications and transcompiles to JavaScript.

TypeScript adds optional types, classes, and modules to JavaScript. These features can be used by developers to help prevent errors in their code and to make their code more maintainable.

TypeScript supports tools for largescale JavaScript applications for any browser, host, and OS.

The TypeScript type system allows developers to catch errors during compilation time.

In this blog post, we'll take a closer look at the TypeScript type system and how it can help you write better code.

When you're writing code in TypeScript, you'll often see the following two terms:

Static type checking: This is the process of checking the correctness of your program at compile time.
Type inference: This is the process of automatically inferring the types of variables and expressions based on their usage.
Static type checking is what enables the TypeScript compiler to catch errors during compilation time. With static type checking, you can catch errors such as:

  • misspelled variable names,
  • calling a function with the wrong number or type of arguments,
  • accessing an object property that doesn't exist,
  • etc.

Type inference is what allows TypeScript to automatically infer the types of variables and expressions based on their usage. This is a big advantage over JavaScript, which doesn't have this feature. With type inference, you don't have to explicitly declare the types of your variables and expressions. The TypeScript compiler will automatically infer their types based on their usage.

For example, consider the following code:

let x = 3;     // The type of x is inferred to be 'number' 
let y = ""foo""; // The type of y is inferred to be 'string' 
let z = x + y; // The type of z is inferred to be 'string' 
Enter fullscreen mode Exit fullscreen mode

In this code, we don't explicitly declare the types of our variables x, y, and z. However, we can see from their usage that the types of x and y are 'number' and 'string', respectively. Similarly, we can see that the type of z must be 'string', since it's the result of adding two values with different types (x is a 'number' and y is a 'string').
Type inference is a powerful tool that can help you write cleaner and more concise code. In many cases, you won't even need to explicitly declare the types of your variables and expressions. The TypeScript compiler will automatically infer their types based on their usage.

Many popular libraries are written in TypeScript including AngularJS 2, Ionic 2, and Lodash

If you are interested in learning TypeScript, there are many resources available online, including the TypeScript documentation, tutorials, and webcasts. Additionally, there are many IDEs and text editors that support TypeScript development.

If you're not already using TypeScript, you're missing out on one of the best parts of the JavaScript ecosystem. Not only will TypeScript make you a more proficient JavaScript developer, but it also comes with a lot of benefits that can make your life easier. So what are you waiting for? Start learning TypeScript today!

Help us out by starring our Github repo!

And join the discussion in our Discord channel
Test your API for free now at BLST!

Discussion (12)

Collapse
lukeshiru profile image
Luke Shiru

I love TypeScript, I worked with it for almost 10 years (using it since it was first released to the public), and yet I wouldn't say that people "should learn" TypeScript. TypeScript is great, but is not for everyone. Some folks prefer to just "type" using JSDocs (like SvelteKit), some folks don't like types at all, etc.

For every point in your article, there will be someone that will disagree:

  • TypeScript makes code easier to read and understand: It depends, because you go from this:
const id = value => value;
Enter fullscreen mode Exit fullscreen mode

To this:

const id = <Value>(value: Value) => value;
// And that's being generous, some folks write it like this:
const id = <T>(value: T) => value;
Enter fullscreen mode Exit fullscreen mode

That's far from being "easier to understand".

  • TypeScript can help you catch bugs early: It helps you catch some bugs, not all of them. You still have to write tests and validations.
  • TypeScript is easy to learn for existing JavaScript developers: Some JS developers really like the flexibility of JS, and when TS is set to strict, it can be quite hard to learn. You constantly run into them saying "I know what I'm doing".
  • The TypeScript compiler is available for free: There are lots of free tools out there, and that doesn't make said tools good or make mandatory to learn them.
  • TypeScript adds optional types, classes, and modules to JavaScript: JS already has classes and modules, and types might not be compelling at all for some folks.
  • Many popular libraries are written in TypeScript including AngularJS 2, Ionic 2, and Lodash: Is not something people usually care about, unless they want to work on open source projects using TS.

Let's say my take is: You shouldn't learn TypeScript, but you might want to take a look at it, it might be what you're looking for (it was for me at least).

Cheers!

Collapse
miketalbot profile image
Mike Talbot

A very well put response Luke, couldn't agree more... I find JS (plus JSDoc) easier for projects that I'll mainly work on alone or in a small team and TypeScript beneficial when many people will work on the project.

Collapse
peerreynders profile image
peerreynders • Edited on

Strangely enough it was Vite which significantly lowered the adoption barrier for me (I don't use VS Code).

esbuild's transform is so fast and the "To hell with the TypeScript errors, lets run this code now!" approach means that I don't constantly get stalled having to explain (right now, this very moment) to TypeScript what I'm doing. It's only once I get far enough into the weeds that I switch gears and go into "type linting" mode to shore up the work already done.

I do think there are two significant milestones in TypeScript competence:

  • type consumer
  • type producer

It's fairly easy to get to the "type consumer" level and that's typically all that's required when duct-taping dependencies together to "make a thing".

Maintainers of dependencies however need to aspire to the "type producer" level and that is quite a bit more work because TypeScript has a lot of concepts that other statically typed languages simply don't need (they aren't trying to bridge the gap to a dynamically typed language where it isn't unusual (and sometimes even desirable) for types to morph at run time).

The less you rely on dependencies and the more you are crafting bespoke capabilities, the more you need to push into "type producer" territory.

The one thing I still don't like is the pressure TypeScript places on coding style. Just recently I ended up this the tight ball of code:

async function rootAndRun<T>(
  timeoutMs: number,
  factory: Factory<T>
): Promise<T> {
  let disposeFn: (() => void) | undefined;
  let timeoutId;
  try {
    return await new Promise((resolve, reject) => {
      createRoot((dispose) => {
        disposeFn = dispose;

        timeoutId = setTimeout(function timeout() {
          timeoutId = undefined;
          reject(new Error('Timed out'));
        }, timeoutMs);

        // queueMicrotask/setTimeout allows `setup` to finish
        // before exercising the reactive graph with `run`
        const run = factory(function done(data, err) {
          if (data === undefined) reject(err);
          else resolve(data);
        });
        if (typeof run === 'function') queueMicrotask(run);
      });
    });
  } finally {
    if (disposeFn) {
      disposeFn();
      disposeFn = undefined;
    }
    if (timeoutId) {
      clearTimeout(timeoutId);
      timeoutId = undefined;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The JavaScript version was neatly pulled apart into separate functions

async function rootAndRun(timeoutMs, factory) {
  let disposeFn;
  let timeoutId;
  try {
    return await new Promise(executor);
  } finally {
    if (disposeFn) {
      disposeFn();
      disposeFn = undefined;
    }
    if (timeoutId) {
      clearTimeout(timeoutId);
      timeoutId = undefined;
    }
  }

  // ---
  function executor(resolve, reject) {
    createRoot((dispose) => {
      disposeFn = dispose;
      timeoutId = setTimeout(timeout, timeoutMs);
      // queueMicrotask/setTimeout allows `setup` to finish
      // before exercising the reactive graph with `run`
      const run = factory(done);
      if (typeof run === 'function') queueMicrotask(run);
    });

    // ---
    function timeout() {
      timeoutId = undefined;
      reject(new Error('Timed out'));
    }

    function done(data, err) {
      if (err != undefined) reject(err);
      else resolve(data);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

When I started typing it, TypeScript was constantly complaining how the same T could end up being different types. It's only once I inlined the functions that TypeScript finally "got it".

So when people claim that TypeScript makes refactoring easier they simply mean that it (likely) rings the alarm bells when something doesn't quite line up after you moved some code around.

However I claim that TypeScript doesn't encourage refactoring—in the sense of authoring code that is broken down into easily digestible chunks.

TypeScript's type inference pushes you toward an "inlined" coding style (which I absolutely despise) because inlined code is easier on the type inference. If you want to break things down into sensibly named and sized chunks it penalizes you with a hefty explicit typing tax which most people aren't willing to pay—leading to heavily inlined code which the JavaScript ecosystem already has enough of.

Thread Thread
lukeshiru profile image
Luke Shiru

Clarification before you read the rest: I'm not nitpicking, I just found interesting that you said that TS forced you to do something you wouldn't do with JS and I decided to code this just for fun:

The one thing I still don't like is the pressure TypeScript places on coding style.

I kinda disagree with this. The example you provided can be separated into different functions with smaller responsibilities, that could be reused a lot in the rest of your app:

import { createRoot } from "solid-js";

/**
 * We use this type when we need a promise executor to be passed around.
 */
type Executor<Value> = (
    resolve: (value: Value | PromiseLike<Value>) => void,
    reject: (reason: unknown) => void,
) => void;

/**
 * Classic util function to have a timeout promise.
 */
const wait =
    (milliseconds: number) =>
    <Value>(executor: Executor<Value>) =>
        new Promise((resolve, reject) =>
            setTimeout(executor, milliseconds, resolve, reject),
        );

/**
 * Making use of `wait`, this function rejects with an error if the time runs out.
 */
const createTimeoutPromise = (milliseconds: number) => {
    const timeout = wait(milliseconds);

    return <Value>(promise: Promise<Value>) =>
        Promise.race([
            timeout((_resolve, reject) => reject(new Error("Timed out"))),
            promise,
        ]);
};

/**
 * Promise wrapper for `createRoot`.
 */
const createRootPromise = <Value>(executor: Executor<Value>) => {
    let rootDispose: (() => void) | undefined;

    return new Promise((resolve, reject) => {
        createRoot(dispose => {
            rootDispose = dispose;

            executor(resolve, reject);
        });
    }).finally(rootDispose);
};

/**
 * Queues a value that might be a function as a microtask, if it isn't a
 * function we just ignore it.
 */
const queueFunction = (maybeFunction: unknown) =>
    typeof maybeFunction === "function"
        ? queueMicrotask(maybeFunction as VoidFunction)
        : undefined;

/**
 * Finally your function makes use of all the above. I made it curried so you
 * can use the same timeout for several factories if you want.
 */
const rootAndRun = (milliseconds: number) => {
    const timeoutPromise = createTimeoutPromise(milliseconds);

    return <Value>(factory: Factory<Value>) =>
        timeoutPromise(
            createRootPromise((resolve, reject) =>
                queueFunction(
                    factory((data, error) =>
                        data === undefined ? reject(error) : resolve(data),
                    ),
                ),
            ),
        );
};
Enter fullscreen mode Exit fullscreen mode

If you take a look, you'll notice that every function and type there can be reused to solve other problems, except rootAndRun.

However I claim that TypeScript doesn't encourage refactoring—in the sense of authoring code that is broken down into easily digestible chunks.
TypeScript's type inference pushes you toward an "inlined" coding style (which I absolutely despise) because inlined code is easier on the type inference.

I also dislike having everything in the same function inlined, but really like to make as much use as possible of type inference. I hope the above example is enough to show that we can separate things quite a bit without any issue. And that is not much about JS/TS, but more about making each function do less, but do it right.

That was actually pretty fun to code 😄 ... cheers!

Thread Thread
peerreynders profile image
peerreynders

TIL: There needs to be a comma after the type parameter for an arrow function otherwise TS thinks it's JSX.

const wait =
  (milliseconds: number) =>
  <Value,>(executor: Executor<Value>) =>
    new Promise((resolve, reject) =>
      setTimeout(executor, milliseconds, resolve, reject)
    );
Enter fullscreen mode Exit fullscreen mode

The error message I was fighting:

"Argument of type T is not assignable to parameter of type T. T could be instantiated with an arbitrary type which could be unrelated to T."

CodeSandBox

Which seems to imply:

"The caller of the function specifies the type parameter, not the implementer"


If you refer back to my TypeScript version please note that I require Promise<T>.

Your version only gives me Promise<unknown>. The moment I put Promise<T> on rootAndRun() everything lights up red. The Promise<T> based on Factory<T> was the point of making rootAndRun() generic.

For Promise<unknown> we don't need generics to begin with. CodeSandbox

unknown is extremely useful under very specific circumstances but when overused it reminds me of the Java pre-Generic Object fiasco in terms of type safety.


that could be reused

How OO of you 😁

In this case reuse isn't the goal as this is only for testing support, so not being coupled via reuse (Beware of the Share) is a good thing.

Also in general I try to stick to the Rule of Three:

  • You must have looked at at least three systems to understand what is common across them (and therefore reusable)
  • It takes three times as much effort to make something reusable as to make it usable
  • You will receive payback after the third release.

In this case the goal was to "unroll" it enough to make it more understandable.

Thread Thread
lukeshiru profile image
Luke Shiru

Here's your CodeSandbox with the minimum fixes I would do to it. The problems I saw:

  1. executor had its own T instead of using the one from rootAndRun and Factory, so that will be taken as different generics even if you name them the same. You can see this by just renaming T to something else (F2).
  2. done has the same problem.
  3. resolve inside done expects T, but done actually passes data as T | undefined, so you have to check if data is defined before passing it to resolve.

Other than that I just added Maybe (an alias for Type | undefined that I always have around), and made some changes here and there to make my linter happy (like using error instead of err).

How OO of you 😁

Trust me, FP is all about reuse. I don't follow a "rule of N", I just try to keep my functions small and make them do one job and do it right which makes them far easier to test, maintain and reuse.

Thread Thread
peerreynders profile image
peerreynders

So my "type parameters inside closures" needs more work; figures. The Maybe makes sense, I'm constantly surprised it's not with the utility types.

I just try to keep my functions small and make them do one job and do it right which makes them far easier to test, maintain …

I'm right there with you in terms of small and focused but I'm not prescient so it's far too easy for me to fall prey to premature reuse/abstraction—OO or functional.

Thank You for the assist!

Collapse
t0nyba11 profile image
Tony B

Great response! I stopped using Typescript a year ago, and couldn't go back. My code is so much cleaner without it (ironically). I think I was trying to solve problems that simply didn't exist for me - or at least didn't exist often enough to justify the overhead.

Collapse
apimike profile image
Mike Rozner

Type script made me a lover not a fighter

Collapse
jwp profile image
John Peters

I love working with TypeScript. It also taught me Javascript so now I know both.
When I build webcomponents I use raw Html and Javascript.

But when working on large applications, I use TypeScript. Intellisense is too good to ignore.

Collapse
gitrog555 profile image
GitRog555 • Edited on

Using a timescript is a sign of the coder's respect for other developers, it is a sign of the programmer's maturity and high level of team responsibility. For me, one source was an example of using a timescript; their authors do my programming homework with understandable hints and clarifications in the code.

Collapse
andrewbaisden profile image
Andrew Baisden

TypeScript made me a better JavaScript developer.