Blog.

Typescript For Javascript Developers

Cover Image for Typescript For Javascript Developers
Bryan Guner
Bryan Guner

What are types, and how do they work in TS?

Brief intro to types

Types are a way to tell correct programs from incorrect before we run them by describing in our code how we plan to use our data. They can vary from simple types like Number and String to complex structures perfectly modeled for our problem domain.

Programming languages fall into two categories: statically typed or dynamically typed.

In languages with static typing, the type of the variable must be known at compile-time. If we declare a variable, it should be known (or inferrable) by the compiler if it will be a number, a string, or a boolean. Think Java.

In languages with dynamic typing, this is not necessarily so. The type of a variable is known only when running the program. Think Python.

TypeScript can support static typing, while JavaScript doesn’t.

Due to the static typing of TypeScript, you will need to try harder to:

  • introduce undefined variables (compile-time warnings help)
  • sum two strings that have numbers in them (like “4” + “20” = “420”)
  • do operations on things that don’t permit them, such as trimming a number.

With static type systems, you can create your own composite types. This enables engineers to express their intentions in more detail.

Explicit types also make your code self-documenting: they make sure that your variables and functions match what is intended and enable the computer to take care of remembering the surrounding context.

Types of TypeScript

TypeScript has a variety of basic types, like Boolean, Number, String, Array, Tuple, etc. Some of these don’t exist in JS; you can learn more about them in the documentation of TypeScript.

In addition to those, here are some other types we want to feature to showcase the expressivity of TS:

Any & Unknown

While any as a type can cover, well, anything that you wish, unknown is its type-safe counterpart.

Whenever you want to escape the type system, any enables you to assign any JavaScript variable to it. It is frequently used to model incoming variables (from third-party APIs, for example) that have not yet been checked and whose type is unknown.

Unknown is a lot like any, but it won’t let you perform any operations on the variable before it is explicitly type-checked.

Void

Void is used when there is no value returned, for example, as the return type of functions that return nothing.

Never

Never is the return type for something that should never occur, like a function that will throw an exception.

Intersection & Union types

These enable you to create custom types to better fit your logic.

Intersection types enable you to put together several basic types in one type. For example, you could create a custom type Person that has a name: string and a phone_number: number. It is equivalent to saying: I want my type to be this and that.

Union types enable for your type to take one of the multiple basic types. For example, you could have a query that returns either result: string or undefined. It is equivalent to saying: I want my type to be this or that.

If you think about types as spaces, all of these types quickly make sense.

Types in TypeScript can be both implicit and explicit. If you do not explicitly write your types, the compiler will use type inference to infer the types you are using.

Writing them explicitly, however, gives benefits such as helping other developers that read your code and making sure that what you see is what the compiler sees.

TypeScript vs. JavaScript

It pays to be pragmatic. Have a look at this graph:

From nowhere, TypeScript is now in the 7th position in GitHub pull requests for Q1 2020, above PHP and C. (Source)

While a considerable cause of this is the support of TypeScript by companies like Microsoft (which created it) and Google, it is supported for a good reason.

3 reasons why you should choose TypeScript over JavaScript

1. TypeScript is more reliable

In contrast to JavaScript, TypeScript code is more reliable and easier to refactor. This enables developers to evade errors and do rewrites much easier.

Types invalidate most of the silly errors that can sneak into JavaScript codebases, and create a quick feedback loop to fix all the little mistakes when writing new code and refactoring.

2. TypeScript is more explicit

Making types explicit focuses our attention on how exactly our system is built, and how different parts of it interact with each other. In large-scale systems, it is important to be able to abstract away the rest of the system while keeping the context in mind. Types enable us to do that.

3. TypeScript and JavaScript are practically interchangeable, so why not?

Since JavaScript is a subset of TypeScript, you can use all JavaScript libraries and code that you want in your TypeScript code.

Most popular JavaScript libraries have types in 2020 – Definitely Typed is a repository with types for a lot of different JavaScript libraries that you can use to make your interactions with them more type-safe.

This means that you can gradually adopt TypeScript in your JavaScript codebase, first adding types to individual modules and then expanding to… consume the known universe, I guess.

Drawbacks of TypeScript

You can’t just take a JavaScript team or a JavaScript repository and instantly switch them to idiomatic TypeScript. There are tradeoffs, and upfront time sacrifices you have to make.

While we can argue about the savings that being explicit about types give you in the long run, in the short run, it does take more time to add them. This is arguably not a huge deal, but it is an argument in favor of JavaScript.

Therefore, you might not choose TypeScript for small projects and prototypes for your own use.

Tests vs. Types

To briefly touch the discussion of testing vs. types: both of these things catch different classes of bugs, so it makes sense to do both in a nonpartisan-like manner.

You can still use both unit testing and more advanced techniques such as property-based testing with TS while keeping the benefits of a static type system.


To sum it up, here’s a quick comparison of both languages:

TypeScriptJavaScriptTS is an object-oriented scripting languageJS is an object-oriented scripting languageDependent language (compiles to JavaScript)Independent language (can be interpreted and executed)Compiled language, cannot be directly executed in a browserInterpreted language, executed directly in a web browserCan be statically typedDynamically typedBetter structured and conciseMore flexible since you are not limited by the type systemHas a .ts extensionHas a .js extensionCreated at Microsoft by Anders Hejlsberg (designer of C#) and maintained by MicrosoftCreated by Brendan Eich (Netscape) and maintained by ECMA (European Computer Manufacturers Association).A fair choice for complex projectsGood to work with small, simple projects

TypeScript quickstart guide

TypeScript compiler

To compile your TS code, you need to install tsc (short for TypeScript compiler). The easiest way to do it is through the terminal. This can be done easily via npm by using the following command:

npm install -g typescript

If you want to use TypeScript with Visual Studio Code, there is a handy guide on their website.

Once you have installed tsc, you can compile your files with tsc filename.ts.

Migrating your files from JavaScript to TypeScript

Let’s say that we want to change the following JavaScript file to TypeScript due to odd behavior:

function my_sum(a, b) { return a + b; } let a = 4; let b = "5"; my_sum(a, b);

Good news. Any JS file is technically a valid TypeScript file, so you’re up to a great start – just switch the file extension to .ts from .js.

TypeScript has type inference, which means that it can automatically infer some of the types you use without you adding them. In this case, it presumes that the function sums two variables of type any, which is true but of no great use right now.

If we want to sum only numbers, we can add a type signature to my_sum to make it accept only numbers.

function my_sum(a: number, b: number) { return a + b; } let a = 4; let b = "5"; my_sum(a, b);

Now, TypeScript provides us with an error.

Argument of type 'string' is not assignable to parameter of type 'number'.

Good thing we found where the error is. :) To further escape errors like these, you can also add type definitions to variables.

let b: number = "5" // Type '"5"' is not assignable to type 'number'. let b: number = 5 // Everything ok.

TypeScript is quite flexible in what it can do and how it can help you. For a less trivial example on how to move your existing JavaScript codebase to TypeScript or use TypeScript to improve your JS code, read this guide.

How to use TypeScript in a browser?

To run TypeScript in a browser, it needs to be transpiled into JavaScript with the TypeScript compiler (tsc). In this case, tsc creates a new .js file based on the .ts code, which you can use any way you could use a JavaScript file.

TypeScript stands in an unusual relationship to JavaScript. TypeScript offers all of JavaScript’s features, and an additional layer on top of these: TypeScript’s type system.

For example, JavaScript provides language primitives like string and number, but it doesn’t check that you’ve consistently assigned these. TypeScript does.

This means that your existing working JavaScript code is also TypeScript code. The main benefit of TypeScript is that it can highlight unexpected behavior in your code, lowering the chance of bugs.

This tutorial provides a brief overview of TypeScript, focusing on its type system.

Types by Inference

TypeScript knows the JavaScript language and will generate types for you in many cases. For example in creating a variable and assigning it to a particular value, TypeScript will use the value as its type.

lethelloWorld = "Hello World";let helloWorld: stringTry

By understanding how JavaScript works, TypeScript can build a type-system that accepts JavaScript code but has types. This offers a type-system without needing to add extra characters to make types explicit in your code. That’s how TypeScript knows that helloWorld is a string in the above example.

You may have written JavaScript in Visual Studio Code, and had editor auto-completion. Visual Studio Code uses TypeScript under the hood to make it easier to work with JavaScript.

Defining Types

You can use a wide variety of design patterns in JavaScript. However, some design patterns make it difficult for types to be inferred automatically (for example, patterns that use dynamic programming). To cover these cases, TypeScript supports an extension of the JavaScript language, which offers places for you to tell TypeScript what the types should be.

For example, to create an object with an inferred type which includes name: string and id: number, you can write:

constuser = {name:"Hayes",id:0,};Try

You can explicitly describe this object’s shape using an interface declaration:

interfaceUser {name: string;id: number;}Try

You can then declare that a JavaScript object conforms to the shape of your new interface by using syntax like : TypeName after a variable declaration:

constuser: User = {name:"Hayes",id:0,};Try

If you provide an object that doesn’t match the interface you have provided, TypeScript will warn you:

interfaceUser {name: string;id: number;} constuser: User = {username:"Hayes",Type '{ username: string; id: number; }' is not assignable to type 'User'. Object literal may only specify known properties, and 'username' does not exist in type 'User'.Type '{ username: string; id: number; }' is not assignable to type 'User'. Object literal may only specify known properties, and 'username' does not exist in type 'User'.id:0,};Try

Since JavaScript supports classes and object-oriented programming, so does TypeScript. You can use an interface declaration with classes:

interfaceUser {name: string;id: number;} classUserAccount {name: string;id: number; constructor(name: string, id: number) {this.name = name;this.id = id; }} constuser: User = newUserAccount("Murphy", 1);Try

You can use interfaces to annotate parameters and return values to functions:

functiongetAdminUser(): User {//...} functiondeleteUser(user: User) {// ...}Try

There are already a small set of primitive types available in JavaScript: boolean, bigint, null, number, string, symbol, and undefined, which you can use in an interface. TypeScript extends this list with a few more, such as any (allow anything), unknown (ensure someone using this type declares what the type is), never (it’s not possible that this type could happen), and void (a function which returns undefined or has no return value).

You’ll see that there are two syntaxes for building types: Interfaces and Types. You should prefer interface. Use type when you need specific features.

Composing Types

With TypeScript, you can create complex types by combining simple ones. There are two popular ways to do so: with Unions, and with Generics.

Unions

With a union, you can declare that a type could be one of many types. For example, you can describe a boolean type as being either true or false:

typeMyBool = true | false;Try

Note: If you hover over MyBool above, you’ll see that it is classed as boolean. That’s a property of the Structural Type System. More on this below.

A popular use-case for union types is to describe the set of string or number literals that a value is allowed to be:

typeWindowStates = "open" | "closed" | "minimized";typeLockStates = "locked" | "unlocked";typePositiveOddNumbersUnderTen = 1 | 3 | 5 | 7 | 9;Try

Unions provide a way to handle different types too. For example, you may have a function that takes an array or a string:

functiongetLength(obj: string | string[]) {returnobj.length;}Try

To learn the type of a variable, use typeof:

TypePredicatestringtypeof s === "string"numbertypeof n === "number"booleantypeof b === "boolean"undefinedtypeof undefined === "undefined"functiontypeof f === "function"arrayArray.isArray(a)

For example, you can make a function return different values depending on whether it is passed a string or an array:

functionwrapInArray(obj: string | string[]) {if (typeofobj === "string") {return [obj];(parameter) obj: string } else {returnobj; }}Try

Generics

Generics provide variables to types. A common example is an array. An array without generics could contain anything. An array with generics can describe the values that the array contains.

typeStringArray = Array<string>;typeNumberArray = Array<number>;typeObjectWithNameArray = Array<{ name: string }>;

You can declare your own types that use generics:

interfaceBackpack<Type> {add: (obj: Type) =>void;get: () =>Type;} // This line is a shortcut to tell TypeScript there is a// constant called `backpack`, and to not worry about where it came from.declareconstbackpack: Backpack<string>; // object is a string, because we declared it above as the variable part of Backpack.constobject = backpack.get(); // Since the backpack variable is a string, you can't pass a number to the add function.backpack.add(23);Argument of type 'number' is not assignable to parameter of type 'string'.Argument of type 'number' is not assignable to parameter of type 'string'.Try

Structural Type System

One of TypeScript’s core principles is that type checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural typing”.

In a structural type system, if two objects have the same shape, they are considered to be of the same type.

interfacePoint {x: number;y: number;} functionlogPoint(p: Point) {console.log(`${p.x}, ${p.y}`);} // logs "12, 26"constpoint = { x:12, y:26 };logPoint(point);Try

The point variable is never declared to be a Point type. However, TypeScript compares the shape of point to the shape of Point in the type-check. They have the same shape, so the code passes.

The shape-matching only requires a subset of the object’s fields to match.

constpoint3 = { x:12, y:26, z:89 };logPoint(point3); // logs "12, 26" constrect = { x:33, y:3, width:30, height:80 };logPoint(rect); // logs "33, 3" constcolor = { hex:"#187ABF" };logPoint(color);Argument of type '{ hex: string; }' is not assignable to parameter of type 'Point'. Type '{ hex: string; }' is missing the following properties from type 'Point': x, yArgument of type '{ hex: string; }' is not assignable to parameter of type 'Point'. Type '{ hex: string; }' is missing the following properties from type 'Point': x, yTry

There is no difference between how classes and objects conform to shapes:

classVirtualPoint {x: number;y: number; constructor(x: number, y: number) {this.x = x;this.y = y; }} constnewVPoint = newVirtualPoint(13, 56);logPoint(newVPoint); // logs "13, 56"Try

If the object or class has all the required properties, TypeScript will say they match, regardless of the implementation details.

Comments:


More Stories

Cover Image for Save Content

Save Content

Bryan Guner
Bryan Guner
Cover Image for JavaScript Basics

JavaScript Basics

Bryan Guner
Bryan Guner