Introduction to TypeScript (Part 2)

Part 2 of a Four-Part Guide for Experienced JavaScript Developers

Ashok Khanna
6 min readMay 29, 2022

We introduced some basic concepts of TypeScript in Part 1, but will now take our understanding to another level. The goal of this part is to make you start thinking in TypeScript.

To do that, we need to understand what TypeScript is. We mentioned in Part 1 that it is a syntactical superset of JavaScript — all JavaScript code is equally valid in TypeScript. The important implication of this is that TypeScript syntax cannot interfere with JavaScript syntax.

For example, in JavaScript, the below bolded deconstruction is used to map the properties first and second in the incoming object to firstName and secondName within the function.

function printName({first: firstName, second: secondName}) {
console.log("Hello, " + firstName + " " + secondName);
}
printName({first: "Ashok", second: "Khanna"});

Given how natural TypeScript feels, we would have naturally expected the below to work:

function printName({first: string, second: string}) { ... }

This will obviously not work due to JavaScript’s use of this syntax pattern. Rather, we need to do this:

function printName({first, second}: {first: string, second: string})

Thus, the first golden rule when thinking in TypeScript is that we should never interfere or violate syntax in JavaScript. TypeScript is an add-on that improves JavaScript but it does not change its core syntax or functionality.

Understanding how the TypeScript Compiler Works

This leads us to our next point — how the TypeScript compiler transpiles TS to JavaScript. By and large (with a few minor exceptions), TypeScript does not add any new features to JavaScript apart from Typing. The TypeScript compiler, after validating all type checks, typically deletes all typing information to convert a TypeScript .ts file to a JavaScript .js file.

For example, the below conversion will occur in the transpilation:

// TypeScript Code:
const x: number = 10;
const y: number = 20;
function add(x: number, y: number): number {
return x + y;
}
// Transpiled JavaScript Code:
const x = 10;
const y = 20;
function add(x, y) {
return x + y;
}

Thus, when thinking in TypeScript be mindful of two golden rules:

  • Never write anything that would interfere or violate JavaScript syntax
  • Never expect anything more from TypeScript than static type checking. Imagine your code without the type annotations and ensure it is written in a way that it will work without them (fortunately, TypeScript will give you enough errors to remind you of this point, the next section being an insightful example)

Intermediate Topic 1: Type Guarding

JavaScript has the function typeof which can be used to query the type of an object at runtime. It can return one of eight values: “string”, “number”, “bigint”, “boolean”, “symbol”, “undefined”, “object” and “function”:

if (typeof strs === "object") { ... }
if (typeof strs === "string") { ... }

Note that null is actually of type Object, so if we want to guard against nulls, we would do something like the following, taking advantage of JavaScript’s implicit coercion of 0, -0, NaN, "", 0n, undefined and null to false:

if (strs && typeof strs === "object") { ... }

Naturally, TypeScript can make use of typeof to narrow down union types such as string | boolean and ensure each case is handled correctly:

function test(object: string | boolean) {
if (typeof object === "string") {
console.log(object.toUpperCase());
} else {
console.log(object);
}
}

This is a very common pattern that one may use in TypeScript, and in this instance, TypeScript is helpful in reminding you to add type guards to ensure each type of input is handled correctly at runtime.

We would expect something similar for our user-defined types, however this is not possible. Both of the below would not work as intended:

if (typeof object === "string" | "boolean") { ... }
if (typeof objet === "fish") { ... }

Rather, we need to stay within the realms of JavaScript (remember TypeScript doesn’t do anything funky, it just adds some type annotations to otherwise legal JavaScript code), such as in the following. Here we use a type predicate (taking the form parameterName is Type) and a type assertion (taking the form parameter as Type) in an otherwise legal JavaScript function that simply tests for whether the required property is present on the object:

function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}

Note how you could write the above type guard simply as the following, and how we use the presence of the swim property as the defining attribute for the type Fish. All is plain JavaScript — the type assertions and predicates in the above simply help TypeScript and its compiler to notify them of this definition, but do not modify the core JavaScript semantics.

function isFish(pet) { return pet.swim !== undefined; }

I hope this long-winded discussion has helped stress that you need to think in terms of legal JavaScript code and use TypeScript as an annotation and type checking engine and not some special language that transforms otherwise illegal JavaScript code into legal JavaScript code (as mentioned earlier, there a few edge cases where TypeScript does do some real transformations — we will get to those in due course).

Intermediate Topic 2: Type Assertions

Type Assertions

Type assertions, which we mentioned briefly above, allow us to convert to a more specific or less specific version of a type and are often used when working with DOM elements where we know more information than the compiler. They can be achieved with the as keyword or with <> as the following two equivalent examples show.

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

Note the below would be illegal as string and number are not more specific / less specific from each other:

const x = "hello" as number;

In the rare instance where this rule may be too onerous, one can assert to any and then to the required type:

const a = (expression as any) as boolean;

One other thing I wanted to mention is that we can remove null and defined types from a type without any explicit checking with the ! operator:

console.log(x!.toFixed());

Rounding it Out: Some Miscallenous Types

To round out Part 2, we will touch upon some miscellaneous types in TypeScript. One thing to note with TypeScript is that the language is made up of a small number of concepts which can be combined in powerful ways. So whilst I am showing you some of the main concepts, start thinking about how and why you may want to combine them.

Lets quickly mention some additional types in TypeScript:

  • The object type refers to any value that isn’t a primitive (string, number,
    bigint, boolean, symbol, null, or undefined). This is different from the empty object type{}, and also different from the global type Object. It’s very likely you will never use Object
  • The unknown type represents any value. This is similar to the any type, but is safer because it’s not legal to do anything with an unknown value
  • The never type represents values which are never observed. In a return type, this means that the function throws an exception or terminates execution of the program
  • The global type Function describes properties like bind, call, apply, and others present on all function values in JavaScript. It also has the special property that values of type Function can always be called; these calls return any

Next Steps

You are well on your way to learning TypeScript. Hopefully these explanations have been relatively straightforward, but I do warn you that it will get a bit difficult when you start applying them yourselves. Hopefully after a few days you will start to get the hang of it.

In Part 3, we will discuss function, object and class typing. Go for a walk and maybe sleep on the above, and come back tomorrow to tackle Part 3!

--

--

Ashok Khanna

Masters in Quantitative Finance. Writing Computer Science articles and notes on topics that interest me, with a tendency towards writing about Lisp & Swift