In TypeScript, both interface
and type alias
can be used to define the shape of an object. However, there are some differences and nuances between them. Here are the key differences:
1. Declaration and Syntax
-
Interface:
- Declared using the
interface
keyword. - Primarily used to describe the shape of objects.
- Can be extended using the
extends
keyword.
- Declared using the
interface User {
id: number;
name: string;
email: string;
}
-
Type Alias:
- Declared using the
type
keyword. - Can represent any type, including primitives, unions, tuples, and intersections.
- Declared using the
type User = {
id: number;
name: string;
email: string;
};
2. Extending and Implementing
-
Interface:
- Can be extended by other interfaces.
- Can be implemented by classes.
interface User {
id: number;
name: string;
email: string;
}
interface Admin extends User {
isAdmin: boolean;
}
class UserAccount implements User {
id: number;
name: string;
email: string;
constructor(id: number, name: string, email: string) {
this.id = id;
this.name = name;
this.email = email;
}
}
-
Type Alias:
- Can create new types using intersections and unions.
- Cannot be implemented by classes directly.
type User = {
id: number;
name: string;
email: string;
};
type Admin = User & {
isAdmin: boolean;
};
const admin: Admin = {
id: 1,
name: "John Doe",
email: "[email protected]",
isAdmin: true
};
3. Merging Declarations
-
Interface:
- Supports declaration merging. If you declare an interface with the same name multiple times, TypeScript will merge them into a single interface.
interface User {
id: number;
name: string;
}
interface User {
email: string;
}
// Equivalent to:
// interface User {
// id: number;
// name: string;
// email: string;
// }
-
Type Alias:
- Does not support declaration merging. Declaring a type alias with the same name multiple times will result in a compiler error.
type User = {
id: number;
name: string;
};
type User = {
email: string;
}; // Error: Duplicate identifier 'User'.
4. Complex Types
-
Type Alias:
- More flexible in defining complex types, such as unions, intersections, and mapped types.
type StringOrNumber = string | number;
type Coordinates = [number, number];
type ReadonlyUser = {
readonly id: number;
name: string;
email: string;
};
-
Interface:
- Primarily used for objects. While it can be used with some complex types through extensions, it’s not as versatile as type aliases for unions and intersections.
5. Usage and Readability
-
Interface:
- Generally preferred for defining the shape of objects and classes.
- Provides a clear and semantic way to define object structures, making the code more readable.
-
Type Alias:
- Preferred for more complex type definitions, such as union types and mapped types.
- Offers more flexibility but can sometimes make the code less readable if overused.
Summary
- Use interface when you want to define the structure of an object and benefit from features like declaration merging and class implementation.
- Use type alias for defining more complex types, such as unions, intersections, and mapped types, or when you need more flexibility in type definitions.
Now, some concrete, real-life examples that illustrate when to use interfaces and when to use type aliases in TypeScript:
1. Interfaces for Object Shapes and Class Implementations
Use Case: Defining the Shape of an Object
When you want to define the structure of an object that is passed around in your application, using an interface is a clear and semantic choice.
interface User {
id: number;
name: string;
email: string;
}
function getUserInfo(user: User): void {
console.log(`User ID: ${user.id}`);
console.log(`User Name: ${user.name}`);
console.log(`User Email: ${user.email}`);
}
const user: User = {
id: 1,
name: "Alice",
email: "[email protected]"
};
getUserInfo(user);
Use Case: Class Implementation
When defining classes that need to adhere to a specific structure, interfaces are very useful.
interface Vehicle {
make: string;
model: string;
year: number;
startEngine(): void;
}
class Car implements Vehicle {
make: string;
model: string;
year: number;
constructor(make: string, model: string, year: number) {
this.make = make;
this.model = model;
this.year = year;
}
startEngine(): void {
console.log(`${this.make} ${this.model} engine started.`);
}
}
const myCar = new Car('Toyota', 'Corolla', 2020);
myCar.startEngine();
2. Type Aliases for Unions, Intersections, and Complex Types
Use Case: Union Types
When you need to define a variable that can hold multiple types, type aliases are ideal.
type Status = 'success' | 'error' | 'loading';
function printStatus(status: Status): void {
console.log(`Current status: ${status}`);
}
printStatus('success'); // Valid
printStatus('error'); // Valid
printStatus('loading'); // Valid
Use Case: Intersection Types
Type aliases are great for combining multiple types into one.
type Identifiable = {
id: number;
};
type Nameable = {
name: string;
};
type User = Identifiable & Nameable;
const user: User = {
id: 1,
name: 'Alice'
};
console.log(user);
Use Case: Tuples and Arrays
For defining tuples and array types, type aliases are straightforward.
type Point = [number, number];
const point: Point = [10, 20];
console.log(point);
type StringArray = string[];
const fruits: StringArray = ['apple', 'banana', 'cherry'];
console.log(fruits);
3. Advanced Examples
Use Case: Conditional Types and Mapped Types
When you need more advanced type manipulation, type aliases are the way to go.
type ApiResponse<T> = {
status: number;
data: T;
};
type User = {
id: number;
name: string;
email: string;
};
type UserResponse = ApiResponse<User>;
const response: UserResponse = {
status: 200,
data: {
id: 1,
name: 'Alice',
email: '[email protected]'
}
};
console.log(response);
type ReadOnly<T> = {
readonly [P in keyof T]: T[P];
};
type ReadOnlyUser = ReadOnly<User>;
const user: ReadOnlyUser = {
id: 1,
name: 'Alice',
email: '[email protected]'
};
// user.id = 2; // Error: Cannot assign to 'id' because it is a read-only property.
Use Case: Function Types
For defining function signatures, type aliases provide a concise way to represent the function type.
type Greet = (name: string) => string;
const greet: Greet = (name) => `Hello, ${name}!`;
console.log(greet('Alice'));
Summary
-
Use
interface
when:- Defining the structure of objects.
- Creating contracts for classes to implement.
- You need declaration merging.
-
Use
type alias
when:- Defining union or intersection types.
- Creating complex types like tuples, arrays, and mapped types.
- You need to represent more than just object shapes, such as function types, conditional types, etc.
By choosing the appropriate construct, you can make your TypeScript code more maintainable, readable, and expressive.