A TypeScript beginner's journey

by Mark — 11 minutes

Before I joined Divotion I had no experience with TypeScript. In general, I knew what it was and how it worked. I liked the concept, but due to other priorities, I never used it in one of my projects. At Divotion, consultants are supported and encouraged to develop themselves in terms of knowledge and skills. When I started at the company, I created my list of learning goals. My first learning goal, on top of the list, was TypeScript. 

When I started learning TypeScript, my colleague Emil helped me understand the important concepts and features of the language. He gave me a presentation that covered the main topics and concepts of TypeScript. This presentation gave me a good foundation to build upon. At his recommendation, I read the book "Learning TypeScript" by Josh Goldberg. This book has the same view on TypeScript as Emil. It dives deeper into the language concepts and provides more background information and the reasoning behind certain concepts. Thanks to Emil's guidance and the book's insights, I was able to develop my skills in TypeScript quickly. In this blog, I will take you on my TypeScript beginner's journey and share tips and tricks to overcome the challenges that a TypeScript beginner may face.

What is TypeScript?

I will try to summarize TypeScript in one paragraph, for all readers that don’t have any experience with TypeScript or are still encountering difficulties with it.

TypeScript is an open-source programming language, it was released in 2012, and it is developed and maintained by Microsoft. TypeScript is a superset of JavaScript, it extends JavaScript with new syntax. The added syntax can be used to define types and to add these types to the JavaScript code. This makes the code more strict. But also can be annoying for TypeScript newbies because you have to add the type specifications everywhere. Fortunately, this annoyance is temporary and adding type specifications quickly becomes second nature. The TypeScript type specifications make the code more readable, and maintainable and prevent you from making errors. The type specifications are also used for autocompletion and type checking. This can increase productivity and reduce errors. The type checking is executed in the compilation phase when the TypeScript code is compiled into JavaScript code. The advantage of this is that errors are caught earlier in the development process. Type checking can also lead to better code quality and more efficient code. 

For a more detailed explanation of TypeScript, I can recommend reading Emil's blog: TypeScript Yin-Yang - How TypeScript really compares to JavaScript

The beginner's journey

In the past few months, I have started using TypeScript in a practical project for a client. During this period I encountered several problems that probably many other TypeScript beginners also encounter. I will describe them below.

Error: Type X is not assignable to type Y

This mistake is easily made while coding, it often occurs when you’re not paying attention to the types. Fortunately, the type checker provides a clear error message when this happens. I will describe two cases where I faced this error message here:

TS2322: Type 'string | undefined' is not assignable to type 'string'

Here is a simplified example of a TypeScript code where this error appears (you can also check the example in the TypeScript playground):

function getGameDescription(gameName: string) {
  const games : {[gameName: string] : string} = {
    "pacman": "Pac-Man is a classic arcade game released in 1980",
    "donkey kong": "Donkey Kong is an arcade game released in 1981",
    "space invaders": "Space Invaders is a classic arcade game released in   1978"
  };

  if (games[gameName]) {
    return games[gameName];
  }
  return;
}

interface Page {
   title: string;
   content: string;
}

const gameDescription = getGameDescription("pacman");
let page: Page = {title: "Pacman", content: gameDescription};
//                                 ~~~~~~~
// TS2322: Type 'string | undefined' is not assignable to type 'string'

In this case, the value for the content property of a Page instance should be of type "string". The function getGameDescription returns a value of the union type "string | undefined" so this is either a "string" or "undefined". 

typescript-beginners-journey-img-1

We can’t assign a value of the union type "string | undefined" to a "string" variable.

My first approach to resolve error messages like this was to use type assertions. For example:

let page: Page = {title: "Pacman", content: gameDescription as string};

But then my colleague Emil explained to me that this is not the right way to resolve these kinds of errors. Type assertions should be avoided because these overrule the TypeScript type checker. So the type checker is trusting the developer and not using the type system to check this type. The same counts for the non-null assertion operator. Using this operator should also be avoided.

Good practices to resolve error messages like this and to make your code more robust are: 

  • Ensure that the function will return a string by removing the return; statement and throwing an error when there is no value with the specified key in the list. 

    throw new Error(`Game with name ${gameName} not found.`);
    
  • Use a type guard to guarantee that the type of gameDescription is not undefined.

    if (gameDescription) {
         let page: Page = {title: "Pacman", content: gameDescription};
    }
    
  • Use a type guard to ensure that the type of gameDescription is a string.

    if (typeof gameDescription === 'string') {
         let page: Page = {title: "Pacman", content: gameDescription};
    }
    

TS2322: Type 'string[]' is not assignable to type 'string'

This error message will appear when you write TypeScript code like in this code snippet:

let consoleNames: string = [
  "SNES",
  "Wii"
 ];

Experienced TypeScript developers will see the mistake directly, but if you're new to TypeScript it can take some time before you will find it.

When working with arrays, it's important to indicate that it's an array type. So specify it using either the [] or Array<> syntax.

let consoleNames : string[] = [
  "SNES",
  "Wii"
 ];

Enums

Enums can be used to define a set of constants in a single place. An enum can contain a group of related values that can be used in the code using a meaningful name. 

I like to use them because you only have to define the group of constants once and you can use them everywhere. It’s easy to trace the enum values in your codebase which helps you to see the data flow of these values in the codebase.

When I started using enums in TypeScript I came across two small issues.

The first one was a similar error as discussed in the previous paragraph: 

Type 'string' is not assignable to type 'Colour'

let colourString: string = "GREEN";
enum Colour {
 Red = "RED",
 Green = "GREEN",
 Blue = "BLUE"
}
let colourEnum: Colour = colourString;

The value for colourString can be stored as a string in the database but should be used as Colour in your application. In this case, we can use type assertion to overrule the type checker and ensure that colourString is used as a Colour typed value:

let colourEnum: Colour = colourString as Colour;

Initializing enum-typed variables

Another issue I came across with Enums is initializing an enum-typed variable. I don’t want to select a value upfront from the list of enumerated values. By default, we can’t assign undefined, because that’s not part of the list of values. So an easy fix for this is to make your variable a union-typed variable, for example: 

let colourEnum: Colour | undefined = undefined

Types vs interfaces

When I started my first TypeScript project, I started having doubts about when to use the "type" keyword and when to use the "interface" keyword. So I checked my learning material and the official TypeScript documentation.

The "type" keyword is used for alias declaration and can create an alternate name for any type, including primitive, union, or intersection types.

The "interface" keyword can be used in similar ways as the "type" keyword but offers more capabilities:

  • Interfaces can be "merged" together,  this can be useful when you are using third-party code like npm packages.

  • The TypeScript type checker can work faster with interfaces because interfaces declare a named type that can be cached.

Based on this knowledge, I now use aliases for union types and intersection types, and for all other type definitions, I use interfaces.

Organization of type definitions

From the moment I created the first TypeScript interface, I wasn’t sure where to put it. It should be located in a logical place that’s easy to find and easy to extend and maintain. After some research, I found several options to organize the TypeScript type definitions:

  1. Global files for all TypeScript enums, types and interfaces. For example:

    ts/enums.ts
    ts/interfaces.ts
    ts/types.ts
    
  2. In a global folder, but separated files for each type definition:

    ts/interfaces/Page.ts
    ts/types/Page.ts
    
  3. In the same (or nested) directory as the implemented feature or component:

    Page/Page.tsx
    Page/Page.interface.ts
    

    In this case, you can put the postfix .interface.ts behind the filename to clarify that the file only contains interfaces.

  4. In the same file as the implemented feature or component

    Page/Page.tsx
    

There is no good or bad approach for this as long as you keep it in a certain structure. Sometimes this structure is already determined by your ecosystem. I prefer option 3, it keeps the type definitions close to the implementation and you still have the type definitions separated from the implementation.

Two string type options: string vs String

typescript-beginners-journey-img2

My code editor, Visual Studio Code, has an autocomplete feature to suggest how to complete the syntax I’m writing. If I want to assign the type string to a property in my interface, it gives me two options: “string” and “String” (with a capitalized S). But which one should I use?

  • "string" is the TypeScript primitive string type, which you can use to type variables, parameters and return values. This is the right one to use for your TypeScript types. The other primitive types, like number, object, and boolean are also all lowercase. 

  • "String" (with a capitalized S) is the JavaScript primitive wrapper object. In JavaScript, you can use this one to create new strings, although this is seen as a bad practice. When you use this one as a TypeScript type, it refers to the type of the primitive wrapper object (instead of a primitive type itself). 

Start with TypeScript?

So should you also start using TypeScript? 

If you have a good foundation in JavaScript, then it’s not a big leap to TypeScript. Especially when you have some knowledge about Object Oriented programming.

When I started I went through three phases. In the first phase, I got a lot of type errors, some of these and some of them were not easy to solve. In the second phase, I recognised the errors, which made it easier to resolve them. And after a while, in the third phase, I was aware of most of the type errors and I prevented them while coding. 

I now really like TypeScript. It helps me to define clear components and data structures, and it prevents a lot of type errors. My code is more readable for others and it also helps me to understand the code I wrote a few weeks ago.

There is also excellent support from tools for TypeScript. Code editors like Visual Studio Code and WebStorm provide supporting features like code completion, debugging, and refactoring. And frameworks like Angular and React offer first-class support for it. 

So if you aren’t using TypeScript yet, I would recommend you start with TypeScript! Need a kickstart of your TypeScript journey? Enroll in our training TypeScript Essentials!


Resources

meerdivotion

Cases

Blogs

Event