TypeScript is an add-on for JavaScript. That is, it includes more features and improvements such as syntax for types. TypeScript runs with a compile-time type checker hence you can avoid or debug errors faster when you add types to your code. According to its official website, it is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale.
In TypeScript, there are several ways to represent objects, one of those ways includes interfaces. An interface controls the property names an object can have, this means that when an object implements an interface, the object conforms to the rules specified by the interface.
Interfaces can help achieve code organization and readability which is crucial for maintaining, collaborating, reusing, and optimizing your codebase. In this article, we'll discuss:
Importance Of Interfaces
Interfaces provide a range of benefits, from improving code consistency and readability to facilitating reuse and catching errors early in the development process. In this section, we'll discuss a few importance of using interfaces in your TypeScript code.
Enforce consistency: Interfaces let you define a common set of properties and methods that objects must implement, ensuring that the structure and behavior of objects of a given type are the same.
Facilitate documentation: Interfaces serve as a place to document the expected properties and methods of an object.
Improve Readability: By providing the expected structure of an object, including its properties, methods, and types, interfaces make code easier to read especially when working with complex codebases or collaborating with other developers.
Catch errors early: Enforcing strict type-checking at compile time, interfaces help catch errors before they make it to production. This can save time, effort, and cost.
Code Reusability: Since interfaces allow you to define custom types that can be used in place of built-in types like
number
andstring
, it can ensure that code is more reusable thereby reducing duplication and also make the code modular - this means you can export and import it.
Syntax And Basic Use Cases Of Interfaces
In this section, we'll get started with interfaces in TypeScript. It's important to understand their syntax and basic use cases - defining and implementing interfaces, and how to use them to enforce type safety in your code.
NOTE: To follow along with the code snippets provided, you can use the official TypeScript playground.
Syntax
Interfaces are defined using the interface
keyword followed by its name, then the body of the interface in curly braces. Here's an example of an interface.
interface User {
firstName: string;
lastName: string;
}
We defined an interface and named it User
, it has properties firstName
and lastName
, both of which are of type string
.
We can now use the User
interface as a natural data type.
interface User {
firstName: string;
lastName: string;
}
//define object that conforms to the specification of interface User
const user: User = {
firstName: 'John',
lastName: 'Doe'
}
Objects using the User
interface as their type must have the same properties(the keys in the object), as specified in the User
interface. If a property is optional, it can be omitted. If one of the properties is missing or if its value isn't the same data type specified in the interface, then the compiler will throw an error. Hence we cannot add extra fields that are not defined in the interface to the object.
Let's try to add a new property that was not initially defined in the interface and see what happens:
interface User {
firstName: string;
lastName: string;
}
const user: User = {
firstName: 'John',
lastName: 'Doe',
id:'123' //new property
}
The above code will cause the TypeScript compiler to throw an error 2322
, since the id
property was not initially declared in the interface:
Type '{ firstName: string; lastName: string; id: string; }' is not assignable to type 'User'.
Object literal may only specify known properties, and 'id' does not exist in type 'User'.(2322)
Basic Use Cases
Now that we already know how interfaces are defined, let's discuss a few use cases.
Use Case: Optional Properties
To make a property optional as discussed earlier, you append ?
to its name as seen below:
interface User {
firstName: string;
lastName: string;
id?: string; //id can now be omitted and the compiler won't throw any errors
}
const user: User = {
firstName: 'John',
lastName: 'Doe',
}
Use Case: Read-only Properties
In cases where we want to make a property read-only
in an interface, we include the readonly
keyword before the name of the property:
interface User {
firstName: string;
lastName: string;
id?: string;
readonly dbid: number;
}
const user: User = {
firstName: 'John',
lastName: 'Doe',
dbid: 22
}
After dbid
's first assignment in the user
object, it cannot be reassigned again. Using the readonly
keyword with a property means the property cannot be modified after its first assignment. You can think of its behavior as the same as the const
keyword in JavaScript. Hence, the code below will throw an error 2540
:
interface User {
firstName: string;
lastName: string;
id?: string;
readonly dbid: number;
}
const user: User = {
firstName: 'John',
lastName: 'Doe',
dbid: 22
}
user.dbid = 4;
Error message:
Cannot assign to 'dbid' because it is a read-only property.(2540)
Use Case: Extending Other Types
When creating new interfaces, we can extend our interfaces so they include properties from other interfaces or types. This way you can extract interfaces that contain the same properties and use them as components for other interfaces.
From the User
interface in previous examples, let's define a new interface Admin
that also contains the firstName
and lastName
properties but also contains a new property role
. We can simply extend the Admin
interface from the User
interface.
See code example below:
interface User {
firstName: string;
lastName: string;
id?: string;
readonly dbid: number;
}
interface Admin extends User {
role: 'admin' | 'user' | 'developer'
}
const admin: Admin = {
firstName: 'John',
lastName: 'Doe',
dbid: 22,
role: 'admin'
}
The Admin
interface now inherits the properties of the interface User
and also includes its own property role
. It's just as if we did:
interface Admin {
firstName: string;
lastName: string;
id?: string;
readonly dbid: number;
role: 'admin' | 'user' | 'developer'
}
Extending an interface is not just limited to other interfaces. They can extend from any object type like normal types, and also classes.
Use Case: Function Types
We can use interfaces to describe function types. That is, we can define the expected parameter type and expected return type of a function in an interface.
See code example below:
interface AddFunction {
(a: number, b: number): number;
}
const add: AddFunc = (a, b) => {
return a + b;
}
We defined an interface AddFunction
that describes how the parameters and return value of any function that utilizes the interface should look like. You see that after we declared the interface, we didn't need to specify the parameter and return type in the add
function anymore.
Note: The names of the parameters in our function do not need to match the ones defined in our function type.
Use Cases: Index Signatures
Index signatures are a way of defining the shape of an object with dynamic property names or when the properties of an object are not known yet.
In the code below, we'll see how we can define an index signature for an object whose properties we don't know yet:
interface Score {
[key: string]: number;
}
const score: Score = {
score1: 1,
score2: 2,
score3: 3
}
In the above example, we defined an interface Score
that has an index signature. The key
is a string, and the value
is a number. This simply means that any object(like the score
object above), that utilizes the interface must have its keys
as type string and values
as type number.
Conclusion
While we've discussed some common use cases of interfaces, you should not that there are many more use cases of interfaces in TypeScript. Whether you're working on a large codebase with multiple developers or you just want to improve the quality of your own code, interfaces can be a valuable tool to have in your knowledge base.
If you're interested in learning more about interfaces in TypeScript, you can explore the documentation and start experimenting with different use cases.
In conclusion, interfaces are a powerful feature of TypeScript that can provide a range of benefits, from improving code consistency and readability to facilitating reuse and catching errors earlier in the development process. By enforcing consistent types across a codebase, interfaces can help ensure that code is easier to read and understand, more modular and reusable, and less error-prone.
If you have any questions, feel free to ask them in the comment section.