In the world of TypeScript, interfaces are like handy blueprints for objects. Imagine you're making a recipe. The recipe tells you what ingredients you need and the steps to follow. Similarly, interfaces define what properties (ingredients) an object should have and what methods (steps) it should perform.
In this exploration of interfaces in TypeScript, we will delve into their syntax, usage, and advanced features. We will discover how interfaces can be employed to create flexible and scalable software solutions, emphasizing the importance of clear and well-defined communication between different parts of a TypeScript application.
Exploring interfaces
Think of an interface as a contract: a set of rules specifying the structure and behavior to which an object must adhere. Interfaces provide a way to define custom data types by declaring the shape of an object. They encapsulate the properties and methods an object should have without providing any implementation.
Interface naming conventions
Before proceeding further, we need to cover interface naming conventions. There are two types of naming conventions. The first kind of naming convention involves naming interfaces in the same way as classes. Under the second kind, interfaces are named in the same way as classes, but with the prefix I, which stands for Interface. Now let's dig further.
How to declare them?
Declare an entity an interface by adding the keyword interface before its name.
interface Rectangle {
id: string;
size: {
width: number;
height: number;
}
}
In TypeScript, interfaces can't contain an implementation. When declaring interfaces, developers specify the properties and methods that objects implementing the interface must have. For example, in the next code snippet there's an error because the object rect doesn't have the id field.
const rect: Rectangle = { // Property 'id' is missing, but required in type 'User'
size: {
width: 20,
height: 30
}
}Optional and readonly properties
Two specific kinds of properties help preserve safe and clean code: optional properties and readonly ones.
- Optional properties: If you don't need a property to be present in every object implementing an interface, you can mark it with the
?sign. Doing so renders this property optional in every implemented object. Readonlyproperties: This kind of property won't change after the object is declared. Just putreadonlybefore the property's name.
interface Rectangle {
readonly id: string;
size: {
width: number;
height: number;
};
color?: string;
}
In this example, color is optional, meaning a Rectangle object can lack this property, as in the snippet below, and id is immutable.
const rect: Rectangle = {
id: '123',
size: {
width: 20,
height: 30,
}
}
rect.id = '122' // Error: Cannot assign to 'id' because it is a read-only property.Index signatures for dynamic properties
There are situations when you need to create an interface for an object, which will have numerous dynamic keys.
interface SomeInterface {
[propertyName: string]: PropertyType;
}
propertyNameis the name of the property, which can be any string.PropertyTypeis the type of the property. It can be any valid TypeScript data type, including primitives, objects, or other custom types.
interface CSSButton {
[key: string]: string
}
const btn: CSSButton = {
border: '1px solid black',
borderRadius: '15px',
padding: '10px 15px'
}
For example, we need to create an interface for a button's appearance. Also, we know that CSS rules can be expanded in the future. In this case, we can add string syntax for all the CSS rules. By taking this approach, we give ourselves the freedom to enhance the object with new properties (string keys) and their corresponding values. The types and number of properties can be arbitrary.
Designing clear and concise interfaces
Interfaces should be concise and focused on a single responsibility. Avoid creating overly complex interfaces that try to cover too many scenarios. Instead, favor smaller, specific interfaces that can be combined or extended as needed. Clear interfaces simplify understanding and usage.
interface Shape {
draw(): void;
}
interface Rectangle {
width: number;
height: number;
}Implementing interfaces in classes
Any class that wants to implement an interface must specify this with the implements keyword. Besides, a class implementing an interface as a type is required to implement it fully. So the class, implementing an interface Rectangle from above, can look like this:
class RectWithArea implements Rectangle {
id: string;
size: {
width: number;
height: number;
};
constructor(id: string, width: number, height: number) {
this.id = id;
this.size = {
width: width,
height: height
};
}
getArea(): number {
return this.size.width * this.size.height;
}
}
Here, the class RectWithArea has an additional property — getArea — that is not defined in the interface. However, the class itself is still valid. The point of the interface is that we must implement the minimum set of necessary parameters. The rest of the parameters can be present in any number of parameters.
Extending interfaces for inheritance
Interfaces can extend other interfaces, inheriting their properties and methods while allowing additional declarations. This enables developers to create specialized interfaces without rewriting common properties and methods.
interface Person {
name: string;
age: number;
}
interface Employee extends Person {
employeeId: number;
role: string;
}
In this example, there's no need to declare name and age in the Employee interface, as it inherits these properties from the Person interface.
Differences between objects and interfaces
In TypeScript, both objects and interfaces are essential concepts, but they serve different purposes and have distinct characteristics.
Objects
-
Objects are instances: An object is an instance of a class or a data structure that holds key-value pairs. It represents a concrete instance of a real-world entity or a data structure in your program.
-
Dynamic properties: Objects in JavaScript and TypeScript can have dynamic properties. You can add, modify, or delete properties at runtime, making them flexible for representing various data structures.
-
Literal syntax: Objects can be created using literal syntax, like
{ key: value }, making them straightforward to define.
Interfaces
-
Blueprints for structure: Interfaces are TypeScript's way of defining a blueprint for the structure of objects. They define the properties and types that an object must have. Interfaces do not exist in the compiled JavaScript code; they are purely a TypeScript concept for type checking during development.
-
Static definition: Once an interface is defined, objects that implement the interface must adhere to its structure. Interfaces provide a way to enforce a specific shape for objects at compile time.
-
Reusability: Interfaces promote reusability by allowing multiple objects or classes to adhere to a common structure. If multiple objects share similar properties, those properties can be defined in an interface, ensuring consistency.
For example:
const person: { name: string; age: number } = {. // Object of a person
name: "Alice",
age: 30
};
interface Person { // Interface Person
name: string;
age: number;
}
const person: Person = { // Object of the type Person
name: "Alice",
age: 30
};When to use interfaces vs. types
If you've already learned about Type Aliases, now you may be wondering if both interface and type can define object shapes, so how should you choose between them?
interface UserPass {
id: number;
name: string;
getPassword() {
return `${name}${id}`
}
}
type PersonInfo = {
id: number;
name: number;
};
A general rule is to use interfaces when you need to declare a contract that other objects can implement or extend. Use types for unions, intersections, and primitive type aliases, which simplify our code and ensure that our data structure and functions match the intended pattern. This distinction ensures clarity in your codebase.
Conclusion
Interfaces in TypeScript — because of their ability to be extended by other interfaces and implemented in classes — enhance the structure and predictability of code. They provide clear blueprints for objects, ensuring that different parts of a program communicate effectively. By defining properties, methods, and even dynamic keys, interfaces promote consistency, collaboration and error prevention. They find practical application in scenarios like API integration, UI components, and collaborative development. Guided by its capabilities, we get intuitive, versatile interfaces that are a pleasure to work with in real-world projects.