In part 1 of this article - read here, we took a look at the fundamental concepts of Typescript, including its syntax, data types, and functions.
Now let's look at some conceptual aspects of types and type systems.
Union and Intersection Types
Union and intersection types can conceptually be thought of as logical boolean operators (AND, OR) as they pertain to types
Union Type is a type that can have multiple possible types. It is denoted using the | symbol.
Let's take a look at an example. Consider this piece of code below
The type Address above can be either a string or a number. When we try to set Adddress to true, we get an error of
Error: Type 'boolean' is not assignable to type 'string | number'
.
Let’s write a function that can operate on strings or numbers as well:
If we try to execute the function printId
by passing a number or a string as an argument, this will work fine.
printId(101); // work fine
printId("202"); // work fine
printId(true); // throws an error
We are going to get an error if we pass any other type other than a string or a number.
Intersection type:
An intersection type is a type that combines two or more types into a single type. It is denoted using the & symbol.
Let's look at an example:
In the example above, the person type is an intersection of name and age
, which means that any value of type person
must have all the properties of name and age.
The james
variable is of type person
and has all the required properties, so it is allowed. However, the kola
variable does not.
Interfaces and Type Aliases
Typescript provides two mechanisms for centrally defining types and giving them useful and meaningful names: interfaces and type aliases.
We will study both concepts in depth, and explain when it makes sense to use each type.
Interfaces and type aliases are both used to define custom types that can be used throughout our codebase.
Type aliases:
Consider this piece of code: {name: string, email: string}
. It's a syntax we’ve used before for type annotations.
This syntax will get increasingly complicated as more properties are added to this type.
Furthermore, if we pass objects of this type around through various functions and variables, we will end up with a lot of types that need to be manually updated whenever we need to make any changes!
Type aliases help to address this, by allowing us to:
- define a more meaningful name for this type
- declare the particulars of the type in a single place
- import and export this type from modules, the same as if it were an exported value.
It can be used to make complex types more readable or to define a type that is used in multiple places in the codebase.
Let's take a look at this example code:
In the code above, LocationType
is a type alias that defines an object with a city property of type string and a cityCode
property of type number. The lagos
variable is of type LocationType
and has all the required properties, so it is allowed.
Lets take a look at how we can use type aliases in functions;
Consider this type alias code syntax ;
Now consider this function
function printContactInfo(info: UserContactInfo)
{ console.log(info) console.log(info.email)
}
const painter = {
name: "kola ojo",
email: "kole@gmail.com",
favoriteColor: "Off White",
}
printContactInfo(painter)
This code defines a function called printContactInfo
that takes an argument of type UserContactInfo
and prints it to the console.
Also, we define an object called painter that has a name, email, and favoriteColor
property.
Here, theUserContactInfo
type is a type alias that is defined at the top.
UserContactInfo
type defines an object that has a name and email property, which is why the painter object is able to be passed as an argument to the printContactInfo
function.
When the printContactInfo
function is called with the painter object as an argument, it logs the entire object to the console using console.log(info) and logs the email property using console.log(info.email).
Since the painter object has an email property, both of these log statements will output the expected value to the console.
This example code demonstrates how type aliases can be used to define custom types in Typescript and how they can be used to create more expressive and type-safe code.
By defining aUserContactInfo
type alias, the code can ensure that any object passed to the printContactInfo
function has the required properties, which helps to catch errors early in the development process.
Interfaces
An interface is a Typescript feature that allows you to define the shape of an object, including its properties and methods.
Let's take a look at an example:
Here in the code above, Person
is an interface that defines an object with a name property of type string and an age property of type number.
The james
variable is of type Person and has all the required properties, so it this work fine.
Lets take a look at another example;
consider the code below:
interface Userinfo {
name: string;
email: string;
}
function printUserInfo(info:Userinfo){
info.name
}
Here we define an interface called UserInfo
with two properties: name and email, both of which are of type string.
We define a function, printUserInfo
that takes an argument of type UserInfo
, and it accesses the name property of that argument using the dot notation (info.name).
This is just how we can access an interface property in Typescript.
Inheritance in interfaces
The way JavaScript class “inherits” behavior from a base class, is the same as what Typescript calls a heritage clause: extends
Let's take a look at an example of how we can use inheritance with classes and interfaces in Typescript.
class Animal {
eat(food: string) {
console.log(`Eating ${food}`);
}
}
class Dog extends Animal {
bark() {
return "Woof";
}
}
const d = new Dog();
d.eat("bone"); // Provide an argument to the eat method
This code illustrates how classes can inherit properties and methods from other classes using the extends keyword.
In the code snippet, we have two classes: Animal and Dog.
The Animal class defines an eat method, which takes a food parameter of type string. This eat method is responsible for logging a message to the console, indicating that the animal is eating the specified food.
The Dog class extends the Animal class. This means that it inherits all the properties and methods of the Animal class, including the eat method. In addition to inheriting, the Dog class also introduces its own method called bark, which returns the string "Woof."
When we create a new instance of the Dog class and assign it to the variable d, this instance inherits all the properties and methods from the Animal class, including the eat method. However, the bark method is specific to the Dog class and is not available on the Animal class.
To demonstrate the code in action, we invoke the d.eat("bone") method. TypeScript recognizes that d is an instance of the Dog class, which extends Animal, and allows us to call the eat method on the d instance while providing the required food parameter, in this case, "bone."
Furthermore, if we call d.bark(), TypeScript recognizes that d is an instance of Dog, which has a bark method, and it returns the string "Woof."
Interfaces and type Aliases, which one to choose:
Interfaces and type aliases are both useful tools for defining custom types in Typescript.
Interfaces are often used to define the shape of an object, while type aliases are used to create more readable or reusable types.
In many situations, either a type alias or an interface would be perfectly fine,
however, If you need to define something other than an object type (e.g., use of the | union type operator), you must use a type alias
If you need to define a type to use with the implements heritage term, it’s best to use an interface
If you need to allow consumers of your types to augment them, you must use an interface.
Top comments (0)