As we explored in the last article, we have learned about TypeScript, why we use it, how it differs from JavaScript, how to install, configure, and run TypeScript, and some basic types in TypeScript.
In this article, we’ll learn in-depth about some of the most common types in TypeScript. They include object
, Array
, Union types
, enums
, tuples
, any
type, Literal types
, and Type Aliases.
object
TYPE:
DEFINITION:
An
object
is a type of all non-primitive values (primitive values are undefined, null, booleans, numbers, and strings).
To define an object
type, we simply list its properties and their types. To understand this, take a look at the following examples mentioned below:
IMPLICIT TYPE:
Firstly, open VS Code and create an index.ts
file with the following code:
const course = {
name: "Learn typescript",
price: 20,
};
console.log(course);
As you hover over the course variable, you will see TypeScript has inferred the type of that variable.
Even though we haven't explicitly stated that the course
variable will have name
and price
keys, the TypeScript type inference system handles writing these types.
Visually, it looks like a JavaScript object. However, if you notice it has ;
at the end of the key-value pair, and the value of the key is nothing but a type
of that particular key. The name
key is of type string
and the price
key is of type number
.
You will see autosuggestions from your IDE if you add a .
after the course
variable since the IDE is aware of the keys the course
variable possesses.
The Compilation error with a descriptive message will appear if you attempt to access the key that does not exist in the course object. This is unlike JavaScript, which ignores the error without warning.
EXPLICIT TYPE:
Instead of having TypeScript infer the type for us, let's explicitly specify what type the course
variable must have.
const course: {
name: string;
price: number;
isPublished: boolean;
} = {
name: "Learn typescript",
price: 20,
};
console.log(course.name);
We have assigned a boolean type with the key name isPublished
to the course
variable. Since we explicitly stated that the course
variable must have name
, price
, and isPublished
properties, TypeScript detects this as a compilation error since we are only passing the name
and price
.
Let's say you want to keep the isPublished
property optional. In that case, you can add a ?
before :
to make that property optional as follows:
Furthermore, if you hover over the isPublished
key, you will see that TypeScript has inferred its type as boolean | undefined
. The reason is that isPublished
is optional, and you may not pass any value to it. When that happens, you will receive an undefined
value, and for that TypeScript warns you that the value of isPublished
maybe undefined
.
isPublished
is called an optional property in the above example. In contrast,boolean| undefined
is aunion type
which we will be exploring at great length in this blog.
NESTED object
types:
JavaScript also supports nested objects, which means objects can be nested within objects. Let's take a look at how we assign a type when dealing with such nested objects:
const course: {
name: string;
price: number;
isPublished?: boolean;
student: {
name: string;
age: number;
};
} = {
name: "Learn typescript",
price: 20,
student: {
name: "John",
age: 30,
},
};
console.log(course);
In the above example, we have course
which is an object, inside which we have the student
key, which itself is another object
with the name
and age
keys.
So now, if you try to access student
, you will also get autosuggestions for the student
object which is nested inside the course
object.
Also, typescript knows the type of values that we are getting in all the keys of the course
variable so it can also suggest possible methods for a particular type of values:
Array
TYPE:
DEFINITION:
Array
refers to a special type of data type that can store multiple values of different types sequentially using a special syntax.
SHORTHAND SYNTAX type[]
:
Take a look at the following code:
const course = {
name: "Learn typescript",
price: 20,
tags: ["typescript", "javascript"],
};
console.log(course);
Now, when you hover over tags
, you will see that it says string[]
It is a shorthand and widely used syntax when assigning a type to an array. A string[]
notation implies that the array contains only string
elements.
Since "typescript"
and "javascript"
are strings
, it infers tags
key as a string[]
(array of strings).
GENERIC SYNTAX Array<type>
:
Another way to specify a type for an array is to use generic syntax, such as:
const course: {
name: string;
price: number;
tags: Array<string>; // this is generic syntax Array<type_of_elements>
} = {
name: "Learn typescript",
price: 20,
tags: ["typescript", "javascript"],
};
console.log(course);
Here, we specify that tags
is an Array
that contains only string
type elements.
The <>
indicates that this is a Generic type, and we must specify the type of elements to include in this array. (We will study Generic types in depth in future blogs)
The following error will occur if the element type is not provided:
TypeScript generally infers the type of an array with shorthand syntax
Array
WITH MIXED TYPES:
What if the array has elements of different types? For instance,
const course = {
name: "Learn typescript",
price: 20,
tags: ["typescript", "javascript", 20, 10],
};
Hovering over tags
reveals its type as (string | number)[]
(Again, string | number
is a union type, which means the array includes elements which can be of type string
or number
).
We need something called type guards for handling union types. This is a bit advanced, so we will cover it in a later blog post.
Array
OF object
:
The types of objects that should be included in an array can be explicitly specified. This allows us to avoid runtime errors and speed up development.
To understand how to define a type for an array of specific objects, let's take an example:
const course1 = {
name: "Learn typescript",
price: 20,
tags: ["typescript", 20, 10],
};
const course2 = {
name: "Learn javascript",
price: 20,
tags: ["javascript", 10],
};
const courses: {
name: string;
price: number;
tags: (string | number)[];
}[] = [course1, course2];
console.log(courses);
We have two objects here, course1
and course2
, whose type is { name: string; price: number; tags: (string | number)[]; }
.
Then we have another variable courses
that contains a list of these two courses, so let's create an array with each element being of a type { name: string; price: number; tags: (string | number)[]; }
The type is now known, and we know that courses
should be Array
of type { name: string; price: number; tags: (string | number)[]; }
Therefore, we assigned { name: string; price: number; tags: (string | number)[]; }[]
as the type of courses
variable.
As shown below, TypeScript will automatically infer the type for you if you don't explicitly mention it:
Because TypeScript knows what type of element the courses
array has, you will get autosuggestions not only for courses
but also for each element in the courses
array when you perform any operation on it.
tuple
TYPE:
DEFINITION:
A
tuple
type is a type ofArray
that knows how many elements it contains, as well as the type of elements contained at specific positions.
Here is how we can declare a variable as a tuple:
let course: [number, string] = [1, "Typescript"];
Here, we are saying that the course is an array of fixed length and must have the first element of type number
and the second element of the type string
.
If you try to replace the element at the index 0
which is of type number
with a string
, you will get a compilation error because we explicitly specified that the element at the index 0
must be a number
.
EXCEPTION IN tuple
:
A tuple
should indeed be immutable, but it is still possible to push
an element in it because TypeScript doesn't throw a compilation error when you call the .push()
method.
However, if you attempt to reassign the variable with a different structure, you will receive the following compilation error:
When reassigning, you need to follow the same structure as when assigning.
POSSIBLE USE CASE OF tuple
:
If you need exactly x
amount of values in an array, and you know the type of each element at a specific index, then you may want to consider using tuple
type instead of an Array
type, for more strictness.
enum
TYPE:
DEFINITION:
Enums are one of the few features TypeScript has which is not a type-level extension of JavaScript.
By using enums, we can create sets of constants with names. You can create cases and documentation more easily by using enums.
NUMERIC ENUMS:
An enum can be defined using the enum
keyword. Take a look at the following code:
enum Role {
ADMIN = 1,
READ_ONLY,
AUTHOR,
}
const user: {
name: string;
age: number;
role: Role;
} = {
name: "Max",
age: 30,
role: Role.ADMIN,
};
We have a numeric enum
where ADMIN
is initialized with 1. From that point forward, all of the following members will be auto-incremented. Thus, Role.ADMIN
has the value 1, READ_ONLY
has 2, and AUTHOR
has 3.
Once we define an enum
type, in the above case its Role
, we don't need to remember what the value of any particular role is, we can simply access enum variables with their names such as Role.ADMIN or Role.READ_ONLY or Role.AUTHOR.
You will also get autosuggestions from your IDE to do so as follows:
In addition, if you want to change values for the enum variable, you can do it in your enum
type Role
. You don't have to make that change everywhere in the code.
STRING ENUMS:
In the same way as numeric enums
, enums can have strings as values. Here's an example:
enum Role {
ADMIN = "admin",
READ_ONLY = "read_only",
AUTHOR = "author",
}
As the logic remains the same, but in numeric enums
you will get 1
in return from Role.ADMIN
, as in string enums
you will get "admin"
.
It is up to you what you want to assign to each enum.
HETEROGENEOUS ENUMS:
Similarly, you can assign mixed types of values to enums as follows:
enum Role {
ADMIN = 1
READ_ONLY = "read_only",
AUTHOR = 3,
}
You get the point!
any
TYPE:
DEFINITION:
When a value is of type
any
, you can access any property of it, call it like afunction
, assign it to a value of any type, or pretty much anything else as long as it's syntactically legal.
WHY NOT USE any
:
While any
type is flexible, it loses all the advantages that TypeScript offers. As a result, it provides the same experience as vanilla JavaScript, thus eliminating the need for TypeScript.
Take a look at the following code:
let person: any = {
name: "John",
age: 30,
};
console.log(person.canWalk())
In the above example, we are calling the canWalk()
function, which does not exist in the person
variable. However, TypeScript is simply ignoring everything about that particular variable, so we aren't getting any compilation errors.
There is no autocompletion for this variable, which is another downside.
This is a disadvantage of using any
type and you should avoid using any
unless you don't know the type of the variable or parameter.
USE CASE OF any
:
When there are no other options, and no type definitions are available for the piece of code you're working on, choose the
any
type.any
has some practical use cases. For example, if you are getting a response from an API call and you don't know the structure of the response, then you can useany
to disable the type-checking for the response object.However, if you know the type, be explicit about it.
UNION TYPES:
DEFINITION:
TypeScript’s type system allows you to build new types out of existing ones using a large variety of operators. We can create new custom types by combining some of the core types.
SYNTAX:
The |
operator enables us to combine different types and create a new one. In the previous topics, we used this concept:
let strOrNumArray: (string | number)[] = [];
In the above example, we are saying that the strOrNumArray
could contain elements of type either string
or number
So, you won't get a compilation error if you insert elements of type 'number' or 'string'. However, adding a boolean
type will result in the compilation error as follows:
The above code can be made valid by adding another type boolean
after string
and number
to make an array that has the possibility of string
, number
, and boolean
, as follows:
HANDLE COMPILATION ERRORS USING TYPE GUARDS:
When you use union types, you will get a compilation error if the operation is only valid for one of the types you specified.
Let's see an example to understand this. take a look at the following code:
const combineStrOrNum = (param1: string | number, param2: string | number) => {
let result = param1 + param2; // compilation error here
return result;
};
In the above example, by stating that the param1
and param2
are both of type string | number
, we mean that there may be cases when you pass param1
as number
and param2
as string
, and as we saw in our blog on Introduction to TypeScript, using +
between types string
and number
can lead to unexpected behavior.
So, typescript complains that this expression can yield unexpected value.
We can handle this with type guards, which are available in JavaScript as well. Look at the following code:
const combineStrOrNum = (param1: string | number, param2: string | number) => {
if (typeof param1 === "number" && typeof param2 === "number") {
console.log(param1 + param2); // return sum of 2 numbers
} else if (typeof param1 === "string" && typeof param2 === "string") {
console.log(param1 + " " + param2); // return concatenation of 2 strings
} else {
console.log("Mixed types can not be added"); // return error message
}
};
combineStrOrNum("Hello", "World"); // Hello World
combineStrOrNum(1, 2); // 3
combineStrOrNum("Hello", 2); // "Mixed types can not be added" (unexpected behavior)
Here, the typeof
keyword indicates the type of a particular variable value.
When the type of param1
and param2
is string
, then only we want to concatenate them with a space.
And if param1
and param2
are both numerical, we want to add both of them.
Shortly, we are adding some kind of type guard
to prevent unexpected behavior.
In future blogs, we'll explore in-depth concepts about type guards and other type guards beside
typeof
.
LITERAL TYPES:
DEFINITION:
A literal type is a more concrete sub-type of a union type. The difference is that in union types, we specify the types of variables we are expecting, but in Literal types, we specify the exact value we are expecting.
Let's look at the example to understand this:
SYNTAX
let readOnly: "readonly" = "readonly";
Specifically, we are declaring that you can only assign the "readonly"
string to the readOnly
variable. A string other than "readonly"
cannot be assigned to this variable. If you do so, you will get a compilation error as follows:
USECASE AND IDE SUPPORT:
If you want to accept only predefined values, you can assign a literal type
. For example:
let button: {
label: string;
severity: "primary" | "secondary" | "danger" | "warning";
isSubmit: boolean;
} = {
label: "Submit",
severity: "primary",
isSubmit: true,
};
Using the example above, we are creating a button
variable that has label
- string
, isSubmit
- boolean
, and severity
- "primary" | "secondary" | "danger" | "warning"
.
You will get the following compilation error if you try to assign a different string to the severity
key, even if it is of type string
.
A benefit of the Literal type is that you get autosuggestions for that particular key with all the possible values.
Take a look at the following image:
This is useful in large applications when you don't know which value a given variable can have.
TYPE ALIASES:
WHAT IS IT?:
We might need to use longer types when working with union types
or literal types
. If we want to add another component apart from the button
, let's say an alertBar
that is also having the severity of "primary" | "secondary" | "danger" | "warning"
, it's a bit cumbersome to write it again and again. We can avoid this by using type aliases
.
WHY AND HOW TO USE IT?:
Creating dynamic and reusable code is crucial. Don't-Repeat-Yourself (DRY) is an important principle to follow when writing TypeScript code. You can accomplish this using TypeScript aliases.
A custom type can be created by using the type
keyword followed by the type's name. Let's look at the example below:
type SeverityType = "primary" | "secondary" | "danger" | "warning";
let button: {
label: string;
severity: SeverityType;
isSubmit: boolean;
} = {
label: "Submit",
severity: "primary",
isSubmit: true,
};
let alertBar: {
message: string;
severity: SeverityType;
duration: number;
} = {
message: "This is an error message",
severity: "danger",
duration: 2000,
};
By creating a type
, you can now use SeverityType
anywhere in your code as if it were a number
, string
, boolean
, or any of the primitive or reference types. It's a valid type for TypeScript.
TYPE OF AN OBJECT:
type
can also represent the structure of an object, such as what fields it should contain.
Creating a custom type for an object is as easy as using the type
keyword followed by the type's name and specifying the fields of the type, as well as their types, in the {}
.
In the above example, we can create the following custom types for button
and alertBar
:
type SeverityType = "primary" | "secondary" | "danger" | "warning";
type Button = {
label: string;
severity: SeverityType;
isSubmit: boolean;
};
type AlertBar = {
message: string;
severity: SeverityType;
duration: number;
};
let button: Button = {
label: "Submit",
severity: "primary",
isSubmit: true,
};
let alertBar: AlertBar = {
message: "This is an error message",
severity: "danger",
duration: 2000,
};
We've created two new types in the above code: Button
and AlertBar
, which are type aliases for button
and alertBar
, respectively. Now, we can use those types anywhere in our code without having to rewrite them each time.
We will use
interfaces
instead of types to describe an object's structure in future blogs.
CONCLUSION:
In this blog, we've covered some of the most common types of values you’ll find in TypeScript code.
Types can also be found in many places other than just type annotations. We reviewed the most basic and common types you might encounter when writing TypeScript code. These are the core building blocks of more complex types, which will be discussed in future blogs.
Make sure to subscribe to our newsletter on https://blog.wajeshubham.in/ and never miss any upcoming articles related to TypeScript and programming just like this one.
I hope this post will help you in your journey. Keep learning!