Understanding TypeScript's Interfaces, Types, and Enums
Typescript
In this blog, I will provide a simple explanation of Interfaces, Types and Enums in TypeScript, highlighting the differences between them. Additionally, I'll address when to use each one to aid in your decision-making. Let's start by understanding Interfaces and Types.
Interfaces and Types
In TypeScript, objects are a fundamental way to group and pass around data, and we represent them through object types. Interfaces are a way to define the shape of an object and check the types of its keys. For example:
interface User { name: string; age: number; email : string; isActive: boolean; } // Equivalent to the User interface using types type User = { name: string; age: number; email : string; isActive: boolean; }
Interfaces and Types can also describe functions. For instance:
interface User1 { name: string; age: number; email : string; isActive: boolean; isLoggedOut(a: string): boolean; } type User2 = { name: string; age: number; email : string; isActive: boolean; isLoggedOut(a: string): boolean; } // Describing a function interface somethingAboutUser { (user: User1, createdAt: Date): Promise<boolean>; } type somethingAboutUser2 = (user: User2, createdAt: Date) => Promise<boolean>;
Both Interfaces and Types can be used to check for each key's type in an object. However, Types are more versatile as they can also be used for primitive data types, arrays, and tuples.
// Using Types to define primitive data types type blogName = string; type blogId = number; // Using Types for arrays type NumberArray = number[]; type StringArray = string[]; type CustomObject = { name: string, age: number }; type CustomObjectArray = CustomObject[]; const numbers: NumberArray = [1, 2, 3, 4, 5]; const fruits: StringArray = ['apple', 'banana', 'orange']; const people: CustomObjectArray = [{ name: 'John', age: 30 }, { name: 'Jane', age: 25 }]; // Using Types for tuples type MyTuple = [string, number, boolean]; type MixedTuple = [string, number, boolean, string[]]; const myData: MyTuple = ['Hello', 42, true]; const mixedData: MixedTuple = ['Mixed', 123, true, ['array', 'of', 'strings']];
Extending Interfaces and Types
Interfaces and Types can be extended to create more comprehensive definitions:
interface User1 { name: string; } interface User2 extends User1 { age: number; } type User3 = { name: string; } type User4 = User3 & { age: number; } interface User5 extends User3 { age: number; } type User6 = User1 & { age: number; }
While both Interfaces and Types can be extended, there is a difference in how they are extended. Interfaces use the extends
keyword, while Types use the &
symbol for intersection.
However, when dealing with union types (a value that can be of more than one type), Interfaces are more restrictive and can only extend object types or an intersection of object types with statically known members. In contrast, Types can fit better in scenarios involving union types.
For example:
type User4 = User3 & { age: number; } | { age: string;} // This will give an error. interface User5 extends User4 { age: number; } // This won't give an error. type User7 = User4 & { email: string; }
In TypeScript, both Interfaces and Types can be implemented in classes to enforce a specific structure. However, there is one important distinction: Classes can implement Interfaces, Types, or a combination of both, but they cannot implement union types.
Merging Interfaces
One significant advantage of Interfaces is the ability to merge them. You can define two interfaces with the same name, and they will be merged into one:
interface User { name: string; } interface User { age: number; } const myUser : User = { name: "Mustafa", age: 25 }
Merging interfaces can be helpful when you want to split large interfaces into more manageable parts.
Which one should I use ?
Ultimately, the choice between Interfaces and Types depends on your needs and preferences. Both have their strengths and can be used interchangeably in many situations. If you require merging or a more object-oriented approach, Interfaces may be more familiar to you. On the other hand, if you work with union types frequently or need versatility, Types might fit your project better.
Enums
Enums in TypeScript allow you to define a set of named constants. They provide a straightforward way to represent distinct cases, making it easier to document intent or manage changes in codebases.
There are two types of enums: Numeric and String enums. Numeric enums are the default type.
// By default it will start from the value 0 enum Direction { North, East, South, West, } // You can also set an initializer enum Direction { North = 2, East, South, West } console.log(Direction.East) // This will log 3 // Or custom enums: enum Direction { North = 61, East = 65, South = 82, West } console.log(Direction.West) // This will log 83 // String Enum enum Direction { North = 'North', East = "East", South = "South", West = "West" };
Enums help you avoid static IF
checks and provide an easier way to manage and make changes to your codebases. Instead of comparing string literals, you can compare enum values, which improves code readability and reduces the likelihood of typos.
if (direction === "North") { console.log("Going to North"); } // Use enum values for better readability if (direction === Direction.North) { console.log("Going to North"); }
In conclusion, Interfaces, Types, and Enums all serve different purposes, and the choice of which one to use depends on your specific requirements and coding style. Understanding their distinctions will allow you to write more maintainable and expressive TypeScript code.