TypeScript Intersection Types
Intersection Types combine multiple types into a new type that includes the types of all members.
This is similar to multiple inheritance in object-oriented programming, allowing a type to have the characteristics of multiple types.
SVG Diagram: Intersection Types
Why Do We Need Intersection Types?
In development, a type often needs to have the characteristics of multiple types.
For example, an employee is both a Person and a Worker, and needs to have the properties of both.
Intersection types allow us to combine multiple types into one, satisfying this requirement.
Concept Explanation:Intersection types use the
&symbol to connect multiple types, meaning the new type contains members of all types. This is similar to the concept of multiple inheritance.
Basic Syntax
Use the&symbol to combine multiple types.
Example
// Includes name and age
interface Person {
name: string;
age: number;
}
// Define the Worker type
// Includes company name and salary
interface Worker {
company: string;
salary: number;
}
// Use an intersection type to merge the two interfaces
// The Employee type has all properties of both Person and Worker
type Employee = Person & Worker;
// Create an object that has characteristics of both types
var employee: Employee = {
name: "Alice",
age: 25,
company: "Google",
salary: 100000
};
console.log("Employee: " + JSON.stringify(employee));
Output:
员工: {"name":"Alice","age":25,"company":"Google","salary":100000}
Explanation:An intersection type
A & Bmeans the new type has all properties of both A and B, none can be missing.
Intersection Types vs Interface Inheritance
Intersection types can replace multiple inheritance of interfaces.
Example
interface A {
a: string;
}
// Define type B
interface B {
b: number;
}
// Use an interface to inherit multiple interfaces
// Need to use extends to inherit multiple interfaces
interface AB extends A, B {
c: boolean;
}
// Use an intersection type (more concise)
// Directly use the & symbol to combine types
type ABType = A & B & { c: boolean };
// Both methods create a type that contains all properties
var obj: ABType = { a: "hello", b: 42, c: true };
console.log("Object: " + JSON.stringify(obj));
Output:
对象: {"a":"hello","b":42,"c":true}
Comparison:Intersection types are more concise than interface inheritance, especially when you need to inherit multiple types and add extra properties.
Type Mixing (Mixin Pattern)
Using intersection types to implement the Mixin pattern allows dynamically combining class functionality.
Example
// Accepts any arguments and returns an object
type Constructor = new (...args: any[]) => {};
// Mixin: add timestamp functionality
// Returns a new class that extends Base
function Timestamped<T extends Constructor>(Base: T) {
return class extends Base {
timestamp = Date.now();
};
}
// Mixin: add serialization functionality
// Returns a new class that extends Base, including the serialize method
function Serializable<T extends Constructor>(Base: T) {
return class extends Base {
serialize() {
return JSON.stringify(this);
}
};
}
// Base user class
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
// Combine Mixins
// Create a user class with timestamp functionality
var TimestampedUser = Timestamped(User);
// Create a user class with serialization functionality
var SerializableUser = Serializable(User);
// Combine two Mixins
var FullUser = Serializable(Timestamped(User));
// Create an instance and test
var user = new FullUser("Alice");
console.log("Timestamp: " + user.timestamp);
console.log("Serialization: " + user.serialize());
Output:
时间戳: 17134...
序列化: {"name":"Alice","timestamp":17134...}
Mixin Pattern:This is a powerful pattern that adds new functionality to existing classes without modifying them.
Intersection Types vs Union Types
Special attention must be paid to precedence when combining intersection types and union types.
Example
type StringOrNumber = string | number;
// Intersection type: intersection of incompatible types
// string & number = never (no type is both string and number)
type Both = string & number;
// Define three types
type A = { a: string };
type B = { b: number };
type C = { c: boolean };
// Combine union type and intersection type
// (A | B) & C intersects each branch of the union type with C
type Combined = (A | B) & C;
// The actual result is: { a: string; c: boolean } | { b: number; c: boolean }
// That is, either A + C or B + C
var obj: Combined = { a: "hello", c: true };
console.log("Combined: " + JSON.stringify(obj));
Output:
组合: {"a":"hello","c":true}
Important:Intersecting incompatible types results in the
nevertype. For example,string & numberis invalid.
Practical Intersection Types
Intersection types are often used to create utility types for type transformation.
Example
// Iterate over all properties of T, add ? to make them optional
type Partial<T> = { [P in keyof T]?: T[P] };
// Mapped type: make all properties required
// Iterate over all properties of T, remove ? to make them required
type Required<T> = { [P in keyof T]-?: T[P] };
// Mapped type: make all properties readonly
// Iterate over all properties of T, add readonly
type Readonly<T> = { readonly [P in keyof T]: T[P] };
// Define a config interface
interface Config {
host: string;
port: number;
}
// Use utility types
var partialConfig: Partial<Config> = { host: "localhost" };
var requiredConfig: Required<Config> = { host: "localhost", port: 8080 };
var readonlyConfig: Readonly<Config> = { host: "localhost", port: 8080 };
console.log("Partial: " + JSON.stringify(partialConfig));
console.log("Required: " + JSON.stringify(requiredConfig));
console.log("Readonly: " + JSON.stringify(readonlyConfig));
Output:
部分: {"host":"localhost"}
必填: {"host":"localhost","port":8080}
只读: {"host":"localhost","port":8080}
Utility Types:The TypeScript standard library provides many utility types based on intersection types and mapped types, such as Partial, Required, Readonly, etc.
Notes
- Incompatible types:Intersecting incompatible types results in never
- Precedence:Union types have higher precedence than intersection types
- Method conflicts:If two types have methods with the same name, conflicts need to be handled manually
Best Practices:Intersection types are suitable for combining multiple interfaces or types. When multiple inheritance is needed, prefer intersection types over interface inheritance.
Summary
Intersection types are a powerful type composition tool in TypeScript.
- Syntax:Use the
&symbol to connect multiple types - Merging:The new type contains members of all types
- Mixin:Can be used to implement functional composition for classes
- never:Intersecting incompatible types results in the never type
Recommendation:Using intersection types wisely can create flexible type compositions, but be careful to avoid never types caused by unnecessary intersections.
Other Extensions