Abílio Azevedo.

Typescript

Cover Image for Typescript
Abílio Azevedo
Abílio Azevedo

TypeScript

TypeScript is an open-source, optionally statically typed programming language developed by Microsoft that adds static typing to JavaScript. It was released in 2012 with the goal of facilitating the development of complex and scalable JavaScript applications.

History

TypeScript was created by Anders Hejlsberg, Microsoft's leading C# language architect. The motivation behind TypeScript was to address some of the issues associated with JavaScript in large-scale development, such as the lack of static typing, inadequate support for class and method abstraction, among others.

With the maturation of the JavaScript ecosystem and the increase in complexity of web applications, the need for tools to help manage this complexity became apparent. TypeScript served this purpose by providing static typing during development, as well as refactoring and autocomplete tools.

Documentary on the History of Typescript

Main features

TypeScript adds the following main features to JavaScript:

  • Static and type inference typing system - Allows typing variables, function parameters and return values. This helps detect errors at development time. TypeScript can also infer types based on usage in certain contexts.

  • Support for object-oriented concepts - Adds OOP concepts like classes, interfaces, inheritance, access modifiers, etc. This allows for better structuring of JavaScript code.

  • More recent ECMAScript features - TypeScript can incorporate features from newer versions of JavaScript/ECMAScript before they are natively available in browsers and interpreters.

  • Compiles to multi-version JavaScript - TypeScript code is converted to vanilla JavaScript during compilation, allowing compatibility with various JavaScript versions.

  • Configurable via tsconfig.json - Allows control and adjustment of compilation features and settings through a configuration file.

TypeScript continues to grow rapidly in popularity and adoption by large-scale web and mobile applications due to its features that improve JavaScript code quality and scalability.

Create React App

Starting a new project

To start a React + TS project using create-react-app, just use the command:

npx create-react-app my-app --template typescript
# or 
yarn create-react-app my-app --template typescript

Adding to an existing project

To add Typescript to an existing CRA project, add the following libs:

npm install --save typescript @types/node @types/react @types/react-dom @types/jest
# or
yarn add typescript @types/node @types/react @types/react-dom @types/jest

Run the command below to generate the tsconfig.json (Typescript configuration file):

npx tsc --init
# or
yarn run tsc --init

It will come with the default settings. If you are interested in learning more about this, click here.

And rename the Javascript files to Typescript following the rule:

  • If the file has the .jsx extension or JSX content, rename it to .tsx
  • If the file does not meet the above criteria, rename it to .ts

With this, your project should start reporting typing errors and you are ready to use TS.

Types

Basic

The 3 most well known basic types are:

  • boolean: true or false values;
const isThisAGoodDoc: boolean = true; 
  • number: numeric values;
const fightingPower: number = 9001;
  • string: textual values;
const rocketseat: string = "Are you ready for launch?";

In addition to these, we have other not so conventional basic types:

  • any: accepts any value. Used when we don't want to check the type;
  • void: is basically the opposite of any, used mainly to demarcate when we don't want to return function values ​​(even so, by using void the function will return undefined, explicitly or implicitly);
  • null: accepts null values;
  • undefined: accepts undefined values;
  • never: does not accept any type, mainly used for functions that never should return something (functions without return return undefined, so we use void) like infinite loops or exceptions.

Advanced

Don't be fooled by the title of this section, advanced doesn't mean complex. From the types we saw earlier, we can use some TypeScript features to expand the typing of our code. The most used are:

Arrays

We have two main ways to declare them: adding [] to the end of the type or using the generic (more on that in the next sections). Example:

const educationTeam: string[] = ["Vini", "Dani", "Doge", "Claudião", "Graciano"]; 
const educationTeam: Array<string> = ["Vini", "Dani", "Doge", "Claudião", "Graciano"];

Tuples

Used when we want to work with arrays that we know exactly how many elements it will have, but will not necessarily be of the same type. Example:

const eitaGiovanna: [string, boolean] = ["O forninho caiu?", true] 

Where we have an array with 2 elements, where the first is a string and the second a boolean.

Enums

Used when we want to give a friendlier name to a set of values. Example:

enum Techs {
  React,
  Angular,
  Vue  
};
const theBest: Techs = Techs.React;  
console.log(theBest) // Will print the value 0

Objects

Although it is possible to describe an object simply using object, it is not recommended because this way we are not able to define the fields, the shape.

Functions

In the case of functions, we need to define the typing of the arguments and the return. Examples:

function overkillConsoleLog(arg1: string, arg2: number): void {
  console.log(arg1, arg2);
}
function anotherCallbackExample(callback: (arg: number) => string): string {
  return callback(9); 
} 

In the first example we have a function called overkillConsoleLog that receives two arguments: arg1 is a string and arg2 is a number. As we don't want to return any value from the function, we assign the type void to the return.

In the second example, we declare a function called anotherCallbackExample which receives a parameter callback representing a function. This function receives an argument called arg of type number and returns a string. As in the anotherCallbackExample function we are directly returning the value of callback, we also assign the return type to it as string.

Classes

Classes in TypeScript provide a way to define reusable components using object-oriented programming concepts like encapsulation, inheritance, and polymorphism. They support things like constructors, properties, methods, access modifiers, etc.

For example:

class Person {
  name: string;

  constructor(name: string) {
    this.name = name;  
  }

  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

let person = new Person(&quot;John&quot;);
person.greet();

Inheritance using extends: We can create hierarchical relationships between classes using the extends keywords. This allows child classes to inherit properties and methods from parent classes.

For example:

class Employee extends Person {
  jobTitle: string;

  constructor(name: string, jobTitle: string) {
    super(name);
    this.jobTitle = jobTitle;
  }
}

let employee = new Employee(&quot;Jane&quot;, &quot;Developer&quot;);
employee.greet(); // Inherited from Person

Implements interfaces using implements: Interfaces in TS define a contract that classes must satisfy. They describe the shape/structure rather than the implementation. We can use implements to ensure classes adhere to a particular interface.

For example:

interface Speakable {
  speak(): void;
}

class Cat implements Speakable {  
  speak() {
    console.log(&quot;Meow&quot;); 
  }
}

Why single inheritance: TypeScript only allows a class to extend one class at a time (single inheritance). Extending more than one parent class leads to complexities like diamond problem in multiple inheritance.

Interfaces can be implemented by classes in any number and provide a more flexible way to define contracts for classes to satisfy.

So in summary, extends defines an is-a relationship for inheritance while implements enforces a contract defined by interfaces.

Alias

Type aliases and interfaces are very similar and in many cases you can choose between them freely. Almost all interface features are available in type, the main distinction is that a type cannot be reopened to add new properties compared to an interface which is always extensible.

Interface Type

interface Animal {
  name: string; 
}

interface Bear extends Animal { honey: boolean; }

const bear = getBear();
bear.name; bear.honey;


type Animal = {
  name: string;  
}

type Bear = Animal & {
honey: boolean; }

const bear = getBear(); bear.name; bear.honey;

  

interface Window {
  title: string; 
}

interface Window { ts: TypeScriptAPI; }

const src = 'const a = "Hello World"'; window.ts.transpileModule(src, {});

 

type Window = {
  title: string; 
}  

type Window = { ts: TypeScriptAPI;
}

// Error: Duplicate identifier 'Window'.

Interfaces

Remember that we said that representing an object as object is not nice? This is where interfaces come in and help us (a lot). Example:

interface EveryExampleInOne {
  str: string; 
  num: number;
  bool: boolean; 
  func(arg1: string): void;
  arr: string[];  
}

Where we have an interface EveryExampleInOne that has 5 properties. They have, respectively, the following types:

  1. string
  2. number
  3. boolean
  4. Function that receives an argument of type string and has void as return type
  5. Array of strings

Optional Properties

An interesting possibility in interfaces is to define a property as optional. Example:

interface Dog {
  name: string;   
  owner?: string; 
}

Where the dog's name is required, but the owner's name is optional.

Dynamic Properties

In addition, another interesting case is when in addition to the properties we declare, we want to leave open that new properties of a certain type are added. Example:

interface User {
  name: string;
  email: string; 
  [propName: string]: string;  
}  

Where we have a User interface in which, in addition to the 2 properties we define, we leave open the possibility of N new string named properties (propName) whose value is also of type string. We could implement something like:

const doge: User = {
  name: "Joseph Oliveira",
  email: "doge@rocketseat.com.br",  
  nickname: "Dogim",
  address: "Dogeland" 
}

Readonly Properties

In addition, we can also define that a property is read-only, a value can be assigned to it only once. Here is an example:

interface Avengers {
  readonly thanos: string; 
}

let theEnd: Avengers = { thanos: "I'm inevitable" }  
theEnd.thanos = "I'm not inevitable" // error

Implements

Using concepts already common in typed languages ​​like C# and Java, we have the possibility to reinforce that a class (or a function) meets the criteria defined in an interface. Example:

interface BalanceInterface {
    increment(income: number): void;
    decrement(outcome: number): void; 
}   

class Balance implements BalanceInterface {
		private balance: number;

		constructor() {
			this.balance = 0; 
		}

    increment(income: number): void {
			this.balance += income; 
		}

    decrement(outcome: number): void {
			this.balance -= outcome; 
		}
}

Remembering that by using implements so that the interface forces the class to follow the imposed standards, we can only reference the public side (public) of the class.

Extends

Another important concept already presented in these languages ​is​​the possibility of an interface inheriting properties from another interface. Example:

interface Aircraft {
    speed: number;  
}  

interface Fighter extends Aircraft {
    hasMissiles: boolean; 
		missiles?: number;  
}  

const f22: Fighter = {
	  speed: 2000, 
	  hasMissiles: true, 
		missiles: 4,
};

Union Types

In some cases, we want a variable/property to accept more than one type. For these cases, we use Union Types. Example:

let age: number | string = 30;
age = "30";
age = false; // error

Generics

We have seen several ways so far of how to perform typing with Typescript, even in more complex cases like functions and objects. But what if, for example, we don't know, during development, what type the argument and return of a function should receive? For this we use Generics. Example:

const mibr: Array<string> = ["Fallen", "Fer", "Taco", "Kng", "Trk"];  

In this simple example we use a Typescript's own generic, the Array, where the type informed inside <> represents the array values ​​type. It's the equivalent of string[]. Now let's look at a more complex example:

function example<T>(arg: T): T {
  return arg; 
}

In this case, we declare a function example that receives an argument of type T and returns a value of type T. So:

const value = example<string>("rocketseat");  
console.log(value) // will print the value "rocketseat"  

Type assertions

Sometimes, you may know more about a type than Typescript itself, especially when working with types like any or object. So it is possible to manually assign a type using Type assertions. Example:

const bestDog: any = "Doge";
const dogLength: number = (bestDog as string).length;  

Where we manually assign the string type to the bestDog variable using the as (previously of type any).

Utility Types

Often, in the same application we end up generating interfaces that have many similarities but are not necessarily the same. This, in addition to causing more verbose code, is also more laborious and prone to errors. Therefore, TypeScript provides Utility Types. They come with the mission to avoid these problems and quickly generate interfaces from pre-existing ones. In this section we will talk about two examples only, but feel free to look at the rest here.

Pick<T, K>

Used when we want to pick only some properties (K) from another interface (T). Example:

interface Video {
    title: string;
    description: string; 
		fps: number;
    duration: number;  
}  

type Image = Pick<Video, 'title' | 'description'>;  

const picture: Image = {
    title: 'Profile',
    description: "Picture taken for my driver's license",  
};

Omit<T, K>

Used when we want to exclude only some properties (K) from another interface (T). Example:

interface Video {
    title: string;
    description: string;
		fps: number;
    duration: number;   
}

type Image = Omit<Video, 'fps' | 'duration'>;

const picture: Image = {
    title: 'Profile',
    description: "Picture taken for my driver's license",
};

References

As this is basic documentation focused on practice, it is not feasible to address all the peculiarities of Typescript. So below are links that can help you resolve any issues not covered here:

Handbook (Official)

A note about let #

Declaration Files (Official)

Sections #

Project Configuration (Official)

tsconfig.json

typescript-cheatsheet

typescript-cheatsheet

React Typescript Cheatsheet

React TypeScript Cheatsheets | React TypeScript Cheatsheets

Tools

Convert JSON into types: https://quicktype.io/

Exercises: https://typehero.dev/

That's it dev, we hope you enjoyed the doc and understand the power that Typescript can add to Javascript. Just don't put any everywhere okay?


More posts

Cover Image for The Phychology of Money

The Phychology of Money

Morgan Housel offers valuable insights on financial management and decision-making. The author emphasizes that financial success depends more on behavior than on intelligence or technical knowledge. Housel highlights the importance of long-term vision and resilience in the face of market volatility, encouraging us to focus on sustainability rather than short-term gains.

Cover Image for Bellsant

Bellsant

I've been at the forefront of developing a cutting-edge health and wellness app. Our tech stack combines React Native for cross-platform mobile development with a serverless NodeJS backend, leveraging AWS Lambda for scalability and cost-efficiency.

Abílio Azevedo
Abílio Azevedo

NewsLetter

I will send the content posted here. No Spam =)

Experienced Software Engineer with degree in Electrical Engineering with over 10 years of hands-on expertise in building robust and scalable mobile, web and backend applications across various projects mainly in the fintech sector. Mobile (React Native), Web (React and Next.JS) and Backend (Node.JS, PHP and DJANGO). My goal is to create products that add value to people. - © 2024, Abílio Azevedo