Guides, TypeScript
Sep 17, 2022
TypeScript Quick Start guide for JavaScript users
Basic information and notes to start using TS
Let an AI answer your questions
Introduction. Why should you use TypeScript?
TypeScript is a FOSS language developed by Microsof that is a syntactical superset of JavaScript. At its core, TypeScript adds static typing and improved tooling to the language.
The main benefits of using TypeScript are:
- Very early identification of bugs and potential problems: TS, thanks to its type system and tooling, quickly detects incongruences and errors in your code, alerting you immediately. This is a big advantage because early identification of bugs avoids carrying problems that will be much harder to debug down the line.
- Predictability: If a variable is declared as a specific type, it will always remain of that type. Significatly improves the likelihood of your code working as intended.
- Type inference: performs implicit typing to save time.
- Readability: helps readability of your code by declaring types on complex data structures.
- Support for advanced data structures
TS is designed for big projects, however, if you learn the basics, I recommended you to use it for a project of any size.
This article is intended to provide some quick notes and examples to help you start using TypeScript in your projects.
Type inference
TypeScript will automatically infer types on basic and common declarations.
const myVar = 'Hello World'; // Automatically assigns type string
const myObj = {
text: 'Hello World',
score: 4,
} // Assigns type string to "text" and number to "score"
Functions
Explicit parameter types
To define parameter types in functions:
function greet (name: string) {
// Function body
}
Optional parameters in Functions
To define a parameter as optional, you use question mark ?
symbol. TypeScript will make the parameter optional, but, when given, it must be of the specified type.
function greet (name?: string) {
// Function body
}
Explicit return types
TS allows you to define types for the returned data of a function (normally this would be inferred, but in some cases you will need to do it manually). After the parentheses that contains the parameters, you can add types for the returned data of your function.
// Standard function
function createGreeting(name: string): string {
// Function body
}
// Arrow function syntax
const createArrowGreeting = (name: string): string => {
// Function body
}
// Remember that you should use void type for functions that do not return any value
Rest parameters
Rest parameter syntax allows a function to accept an indefinite amount of arguments as an array. As such, you should type it as an array:
function smush(firstString, ...otherStrings: string[]){
// Function body
}
Arrays
It is common to see two different syntaxes to type arrays:
let names: string[] = ['Danny', 'Samantha'];
// Or using angle bracket syntax
let names: Array<string> = ['Danny', 'Samantha'];
Multi-dimensional arrays
Review the following example to type multi-dimensional arrays:
let arr: string[][] = [[str1, str2], [str3, str4]];
Tuples
JavaScript does not differentiate between arrays and tuples. However, in TypeScript, arrays and tuples do not have compatible types (it is not possible to assign an array to a tuple even if the elements that they contain are of the same type).
A tuple works like an array but with fixed length. In TypeScript, you will generally use tuples when you need to create a JavaScript array using mixed data types. Review the following example:
let ourTuple: [string, number, string, boolean] = ['Is', 7 , 'our favorite number?' , false];
// TypeScript understands that this is a tuple because we type each element manually
Array of Tuples
The following example combines tuples with arrays:
let danceMoves: [string, number, boolean][] = [
['chicken beak', 4, false],
['wing flap', 4, false],
['tail feather shake', 4, false],
['clap', 4, false],
['chicken beak', 4, true],
['wing flap', 4, true],
['tail feather shake', 4, true],
['clap', 4, true],
];
Enums
Enums are a feature offered by TypeScript that is also common in other languages. It is not just a type-level extension of JavaScript. TS provides numeric and string enums, numeric being the most common.
To declare an enum, use the enum
keyword:
enum Direction {
North,
South,
East,
West
}
TypeScript will automatically assign numbers to each element. In the example above, North will be assigned 0
, South 1
, East 2
, West 3
. It is also possible to manually assign the first element, so that it starts at another value or to assign all elements manually. For example:
enum Direction {
North = 1,
South,
East,
West
// Starts from 1
}
The core feature of enums is that they can be used to perform the following: to type, to assign values and to access values. Review the following example:
enum Direction {
North,
South,
East,
West
// Assigns numeric values starting from 0
function destination (direction: Direction) { // Uses the enum for typing
// Function body
}
destination(Direction.West); // Uses the enum to access a value
let myVar = Direction.West // Uses the enum to assign a value.
}
You can also use string enums. These always have to be assigned manually. Note that by convention we should capitalize the first letter of the variable name:
enum DirectionString { North = 'NORTH', South = 'SOUTH', East = 'EAST', West = 'WEST' }
Objects
Use the following syntax to type objects directly:
let myCompany: {companyName: string, employees: number} = {
companyName: 'myCompany',
employees: 2,
}
However, in situations with complex types, TypeScript provides Type Aliases (for general data) and Interfaces (for data shapes, objects).
Type Aliases and Interfaces
For complex types it is useful to declare them separately in your code to avoid clutter. This is where Type Aliases and Interfaces come in.
type Person = { name: string, age: number }
let myPerson: Person = {
name: 'John',
age: 35,
}
In recent versions of TypeScript, types and interfaces work similarly in practice. However, you should use Types for general data typing, such as arrays and tuples. Interfaces are intended for data shapes such as objects. Inferfaces provide more features, such as declaration merging, extends or implements. If you want to read more on the differences, check this article.
We should rewrite the previous example using interface
. Take note of syntax differences:
interface Person {
name: string;
age: number;
}
let myPerson: Person = {
name: 'John',
age: 35,
}
Interfaces and Classes
Interfaces allow you to type classes using the implements
keyword:
interface Robot {
identify: (id: number) => void;
}
class OneSeries implements Robot {
identify(id: number) {
console.log(`beep! I'm ${id.toFixed(2)}.`);
}
answerQuestion() {
console.log('42!');
}
}
In the example above, the OneSeries
class is required to implement a method named identify
as required by the Robot
interface. The interface sets a minimum requirement, but the class can have additional methods and properties on its own without TS throwing any error.
Generics
Generics allow you to specify a type variable that changes depending on the call. This can be useful in some cases:
function getFilledArray<T>(val: T, n: number): T[] { // T is a type variable.
return Array(n).fill(value)
}
getFilledArray<string>('hi', 6); // T gets string assigned on function call.
getFilledArray<number>(4, 3); // T gets number assigned on function call.
Unions
Unions allow you to define multiple types for the same variable. This feature is very common in TS.
let myVal: number | string;
// number
myVal = 1
// string
myVal = 'Hello World'
Unions and Arrays
This is one practical example of unions: an array that can have multiple types.
const dateNumber = new Date().getTime(); // returns a number
const dateString = new Date().toString(); // returns a string
const timesList: (string | number)[] = [dateNumber, dateString]; // Take note of the syntax
Type Narrowing and Common Key Value Pairs
If we allow multiple data types on a variable, TypeScript by default will only allow us to access common methods and properties that all members of the union share. To access specific methods and properties of a type, we need to perform type checking (also called type narrowing).
function processVal (myVal: string | number) {
if (typeof myVal === 'string') {
let myValUpperCase = myVal.toUpperCase();
}
}
// We would no be able to use toUpperCase method unless we type check beforehand.
Literal Types
You are also allowed to create literal types in TypeScript. In the following example, only the values provided by the type are allowed:
type Color = 'green' | 'yellow' | 'red';
function changeLight(color: Color) {
// ...
}
The in keyword in Type Guards
You can use the in
keyword to check if a property exists in an object anywhere withing its prototype chain. In the following example, the function parameter can take the form of two types of objects, each with a different method. We use the in
keyword to detect if the method exists in the parameter.
type Tennis = {
serve: () => void;
}
type Soccer = {
kick: () => void;
}
function play(sport: Tennis | Soccer) {
if ('serve' in sport) {
return sport.serve();
}
if ('kick' in sport) {
return sport.kick();
}
}
TypeScript can also automatically detect that an else statement is the opposite type guard check, without the need to manually declare it.
Composing Types and Interfaces
You can create types and interfaces and reference them in other types and interfaces. This is essential when you have a lot of nesting.
interface About {
general: General;
}
interface General {
id: number;
name: string;
version: Version;
}
interface Version {
versionNumber: number;
}
Extending interfaces
You can copy all types from one interface to another using the extends
keyword.
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
const mySquare: Square = { sideLength: 10, color: 'blue' };
Index Signatures
With objects, sometimes it's not possible to known the property names beforehand. This is common when dealing with external APIs. But you may still know a basic structure of the data. TypeScript provides a feature called Index Signatures to solve this problem.
We might get data from an API that looks like this:
{
'40.726776': true;
'41.203323': true;
'40.417286': false;
}
The property names are unknown beforehand. To type this data, we can use the following interface. Take note of the syntax using brackets:
interface SolarEclipse {
[latitude: string]: boolean;
}
Conclusion
I hope that this article serves as a quick overview of some of the features that you may need to use when implementing TypeScript in your projects. As I wrote earlier, in my opinion the advantages provided by TypeScript outweigh the extra work required to deal with its type system and I recommend you to use it for your projects.