The statement merging mechanism
TypeScript's declaration merging mechanism allows multiple declarations with the same name to be automatically merged into one, providing significant flexibility in type definitions and code organization. Understanding this mechanism helps in leveraging TypeScript's type system more effectively, especially when extending third-party libraries or modules.
Basic Concepts of Declaration Merging
Declaration merging refers to the process where the compiler combines multiple declarations with the same name into a single definition. This mechanism applies to type declarations such as interfaces, namespaces, classes, and enums. For example:
interface Box {
height: number;
}
interface Box {
width: number;
}
const box: Box = {
height: 100,
width: 200,
};
Here, the two Box
interfaces are merged into one, containing both height
and width
properties. If the types of the same-named properties conflict, the compiler will report an error.
Declaration Merging for Interfaces
Interfaces are the most common scenario for declaration merging. During merging, non-function members with the same name must have consistent types; otherwise, an error will occur. Function members, however, are overloaded:
interface Cloner {
clone(animal: Animal): Animal;
}
interface Cloner {
clone(animal: Sheep): Sheep;
}
// The merged result is:
interface Cloner {
clone(animal: Animal): Animal;
clone(animal: Sheep): Sheep;
}
The order of function overloads depends on the declaration order, with later declarations having higher priority. However, the rules become more complex when literal types or generics are involved.
Merging Namespaces
Namespaces can be merged with other namespaces, classes, functions, or enums of the same name. When merging with namespaces, exported members are combined:
namespace Animals {
export class Zebra {}
}
namespace Animals {
export interface Legged {
numberOfLegs: number;
}
export class Dog {}
}
// The merged result is:
namespace Animals {
export interface Legged {
numberOfLegs: number;
}
export class Zebra {}
export class Dog {}
}
When a namespace is merged with a class, static members are extended:
class Album {
label: Album.AlbumLabel;
}
namespace Album {
export class AlbumLabel {}
}
Merging Classes and Enums
Classes can only be merged with namespaces, not with other classes. Enum merging behaves similarly to interfaces:
enum Color {
Red = 1,
}
enum Color {
Green = 2,
Blue = 4,
}
// The merged result is:
enum Color {
Red = 1,
Green = 2,
Blue = 4,
}
Module Augmentation Scenarios
Declaration merging is often used to extend type definitions for third-party modules. For example, extending vue-router
:
import { Route } from 'vue-router';
declare module 'vue-router' {
interface RouteMeta {
requiresAuth?: boolean;
icon?: string;
}
}
This approach does not modify the original code but provides full type support.
Priority and Limitations of Merging
Priority rules for merging:
- Later-declared interface members have higher priority.
- When merging namespaces, later-exported members override earlier ones.
- When merging a class with a namespace, the namespace must appear after the class.
Limitations include:
- Variables and functions cannot be merged (unless namespaces are used).
- Generic type parameters must be identical for merging to occur.
Complex Example: Extending React Component Props
Extending React component props via declaration merging:
import * as React from 'react';
declare module 'react' {
interface HTMLAttributes<T> {
customAttr?: string;
}
}
function Component() {
return <div customAttr="value" />;
}
This pattern is particularly common in component library development, especially when extending native HTML attributes.
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn