Typescript
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("John");
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("Jane", "Developer");
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("Meow");
}
}
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 |
---|---|
|
|
|
|
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:
- string
- number
- boolean
- Function that receives an argument of type string and has void as return type
- 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 isthe 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)
Declaration Files (Official)
Project Configuration (Official)
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?