If you're like me and are used to functional programming and don't touch OOP, or do it very rarely, then this tutorial is for you.
Objects in Typescript
Let's suppose that a function receives an object, and you want to write some types for the parameters.
function getFirstName(person: {name: string, lastname: string}){
return person.name;
}
Now, this is useful in this function, but what if we want to duplicate this object into a different function?
// Bad example
function getFirstName(person: {name: string, lastname: string}){
return person.name;
}
function getLastName(person: {name: string, lastname: string}){
return person.lastname;
}
This is effective, but is not efficient, because this would mean that if we want to emulate or replicate this literal object type, we would need to define again the same object into every parameter of the functions.
Which would be the most efficient way to replicate that type? We could use types:
type Person = {
name: string,
lastname: string,
}
function getFirstName(person: Person){
return person.name
}
Or we could use interfaces:
interface Person {
name: string,
lastname: string,
}
function getFirstName(person: Person){
return person.name
}
Typescript Interfaces
The examples above are great, but when we need to use one or another? Well, let's suppose you have an address and an extended address types.
type Address = {
city: string,
state: string,
country: string,
postalCode: number
}
type ExtendedAddress = {
street: string,
houseNumber: number,
region: string,
city: string, // The rest of the object from here is the same as the type Address
state: string,
country: string,
postalCode: number
}
We know are looking at some duplicated lines of code. We could save some space if we implement another mechanism that allows us to inherit all of the properties in the Address type and allow us to define new properties besides those ones.
We are looking to extend an interface!
interface Address {
city: string,
state: string,
country: string,
postalCode: number
}
interface ExtendedAddress extends Address {
street: string,
houseNumber: number,
region: string
}
Now, extended address contains all of the properties inside the Address interface!
Extending from multiple interfaces.
Also, another neat trick is that we can as well extend from multiple interfaces!
interface Colorful {
color: string
}
interface Circle {
radius: number
}
interface ColorfulCircle extends Colorful, Circle {} // The curly braces at the end means that we're not passing any properties to the interfaces.
const cc: ColorfulCircle = {
color: "red"
radius: 42
}
Intersection Types
We can achieve the same behavior as above performing an Intersection Type
interface Colorful {
color: string
}
interface Circle {
radius: number
}
type ColorfulCircle = Colorful & Circle;
We can also pass the intersection type directly to the parameters in the function if we believe that is not necessary to produce a new intersection type
function draw(circle: Colorful & Circle) {
console.log(`Color was ${circle.color}`);
console.log(`Radius was ${circle.radius}`);
}
// okay
draw({ color: "blue", radius: 42 });
// oops, misspelled "radius", this will prompt an error.
draw({ color: "red", raidus: 42 });
Generics in typescript
When we want to reuse a type or an interface using different data types, we might use generics to describe this behavior.
function setContents<Type>(box: Box<Type>, newContents: Type) {
box.contents = newContents;
}
const box: Box<string>;
setContents(box, "Hello");
box.contents // "Hello";
We can reuse this easily because we can specify the Type when we call the function.
Nested Generics
One of the most essential things to work in real life projects, is to understand these nested generics. Those could be seen as a pain in the ass to understand (and in some occasions, they are).
But is really not that hard if we know the types we're receiving from the parent types.
type OrNull<Type> = Type | null;
type OneOrMany<Type> = Type | Type[];
// In this next implementation, we're using the OneOrMany generic, that can be one or many of the specified type.
// Here, we have the OneOrManyOrNull type, this could mean that we could expect to have (Type | Type[]) | null
// You could go an replace the types to understand them better!
type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;
type OneOrManyOrNull<Type> = OneOrMany<Type> | null
type OneOrManyOrNullStrings = OneOrManyOrNull<string>;
Array Generic
Typescript allows us to work with some of the pre-defined generics that can be helpful to specify certain behaviors with certain data types, like arrays.
This is the formal definition of the Array generic:
interface Array<Type> {
/**
* Gets or sets the length of the array.
*/
length: number;
/**
* Removes the last element from an array and returns it.
*/
pop(): Type | undefined;
/**
* Appends new elements to an array, and returns the new length of the array.
*/
push(...items: Type[]): number;
// ...
}
An example of this could be like:
function doSomething(value: Array<string>) {
// ...
}
let myArray: string[] = ["hello", "world"];
// either of these work!
doSomething(myArray);
doSomething(new Array("hello", "world"));
We also have some other generics like Map<>, Set<> and Promise<>.
We also have an special type of array generic, the ReadonlyArray!
function doStuff(values: ReadonlyArray<string>) {
// We can read from 'values'...
const copy = values.slice();
console.log(`The first value is ${values[0]}`);
// ...but we can't mutate 'values'.
values.push("hello!");
Property 'push' does not exist on type 'readonly string[]'.
}
We can assign values to a readonly array in its declaration, but we can't modify it or use it as a value
// This is correct.
const roArray: ReadonlyArray<string> = ["red", "green", "blue"];
// This is not correct, ReadonlyArray is intended to be used as a type, not as a value
const someValue = new ReadonlyArray();
If we don't want to use generics, we can use readonly Type[]
function doStuff(values: readonly string[]) {
// We can read from 'values'...
const copy = values.slice();
console.log(`The first value is ${values[0]}`);
// ...but we can't mutate 'values'.
values.push("hello!");
Property 'push' does not exist on type 'readonly string[]'.
}
Tuple Types
You can create tuple types by specifying it like this:
type StringNumberPair = [string, number]
// It doesn't have to be only 2 values, can be more if needed.
function doSomething(pair: StringNumberPair) {
const a = pair[0];
const b = pair[1];
const c = pair[2] // Error, cannot access index 2 on StringNumberPair
}
doSomething(["hello", 42]);
You can also destructure a tuple using destructuring in javascript.
type Point = [number, number]
function doSomething(point: Point){
let [x,y] = point;
}
Top comments (0)