Comparison between type aliases and interfaces
Basic Concepts of Type Aliases and Interfaces
TypeScript provides two main ways to define custom types: type aliases and interfaces. Both can be used to describe object shapes, function signatures, etc., but they differ in usage scenarios and features.
Type aliases are created using the type
keyword:
type User = {
name: string;
age: number;
};
Interfaces are declared using the interface
keyword:
interface User {
name: string;
age: number;
}
Syntax Differences
Type aliases can represent any type, including primitive types, union types, tuples, etc.:
type ID = string | number;
type Point = [number, number];
type Callback = (data: string) => void;
Interfaces focus on describing object shapes:
interface Point {
x: number;
y: number;
}
interface Callback {
(data: string): void;
}
Extension Methods
Interfaces use extends
for inheritance:
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
Type aliases use intersection types &
for composition:
type Animal = {
name: string;
};
type Dog = Animal & {
breed: string;
};
Declaration Merging
Interfaces support declaration merging—identically named interfaces automatically merge:
interface User {
name: string;
}
interface User {
age: number;
}
// The final User includes both name and age
Type aliases cannot be redeclared and will throw an error:
type User = { name: string };
type User = { age: number }; // Error
Implementation and Extension
Classes can implement (implements) either interfaces or type aliases:
interface ClockInterface {
currentTime: Date;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
}
type ClockType = {
currentTime: Date;
};
class DigitalClock implements ClockType {
currentTime: Date = new Date();
}
Representing Complex Types
Type aliases are better suited for representing complex types:
type Tree<T> = {
value: T;
left?: Tree<T>;
right?: Tree<T>;
};
Interfaces can also represent recursive structures but with slightly more verbose syntax:
interface Tree<T> {
value: T;
left?: Tree<T>;
right?: Tree<T>;
}
Tuples and Union Types
Type aliases handle tuples and union types more intuitively:
type StringOrNumber = string | number;
type Coordinates = [number, number];
Interfaces require special syntax to represent these types:
interface StringOrNumber {
/* Cannot directly represent union types */
}
interface Coordinates extends Array<number> {
0: number;
1: number;
length: 2;
}
Performance Considerations
In large codebases, interfaces may perform better than type aliases. The TypeScript compiler has more optimizations for interfaces, especially when checking type compatibility.
Usage Scenario Recommendations
- Choose interfaces when declaration merging is needed.
- Choose type aliases for representing union types, tuples, or other non-object types.
- Both can be used for class implementation, but interfaces align better with traditional OOP thinking.
- Library type definitions typically prefer interfaces for easier extension by users.
Choices in Real Projects
In React projects, Props and State type definitions are often handled like this:
// Using interfaces
interface Props {
title: string;
onClick: () => void;
}
// Using type aliases
type State = {
count: number;
isLoading: boolean;
};
In Redux, action types often use type aliases:
type Action =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'SET_COUNT'; payload: number };
Type Inference Differences
Type aliases and interfaces may behave differently in certain type inference scenarios:
type Named = { name: string };
interface Named {
name: string;
}
function getName(n: Named) {
return n.name;
}
// Literal objects undergo extra property checks
getName({ name: 'Alice', age: 30 }); // Error
const obj = { name: 'Bob', age: 25 };
getName(obj); // Correct
Utility Type Operations
Type aliases work better with utility types:
type PartialUser = Partial<User>;
type ReadonlyUser = Readonly<User>;
type UserWithoutAge = Omit<User, 'age'>;
Interfaces can also work with these utility types, but the result becomes a type alias:
type PartialUser = Partial<IUser>; // Result is a type alias
Function Type Definitions
Both approaches can define function types:
// Type alias approach
type Greet = (name: string) => string;
// Interface approach
interface Greet {
(name: string): string;
}
Function types with properties:
type Counter = {
(start: number): string;
interval: number;
reset(): void;
};
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
Index Signatures
Both support index signatures:
type StringArray = {
[index: number]: string;
};
interface StringArray {
[index: number]: string;
}
Mapped Types
Type aliases are better for creating mapped types:
type Keys = 'name' | 'age';
type User = {
[K in Keys]: string;
};
Conditional Types
Type aliases support conditional types; interfaces do not:
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<123>; // false
Default Exports
When exporting modules, both behave the same:
export type { User };
export interface User {}
Type Queries
The typeof
operator behaves the same for both:
const user = { name: 'Alice', age: 30 };
type UserType = typeof user; // { name: string; age: number }
Type Guards
Type aliases and interfaces behave the same in type guards:
interface Bird {
fly(): void;
}
type Fish = {
swim(): void;
};
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
Generic Constraints
Both can be used as generic constraints:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
type Lengthwise = {
length: number;
};
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
Recursive Types
Type aliases offer more concise syntax for recursive type definitions:
type Json =
| string
| number
| boolean
| null
| Json[]
| { [key: string]: Json };
interface Json {
// Cannot directly represent such complex recursive union types
}
Relationship with Classes
Interfaces can describe both class instances and static parts:
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick(): void;
}
Type aliases can also represent similar structures:
type ClockConstructor = new (hour: number, minute: number) => ClockInterface;
type ClockInterface = {
tick(): void;
};
Type Extraction
Type aliases are more flexible for extracting parts of existing types:
type User = {
id: number;
name: string;
address: {
street: string;
city: string;
};
};
type Address = User['address'];
String Literal Types
Type aliases can conveniently define string literal types:
type Direction = 'north' | 'east' | 'south' | 'west';
Interfaces cannot directly represent such types and require additional definitions:
interface Direction {
value: 'north' | 'east' | 'south' | 'west';
}
Performance Optimization
For large projects, interfaces may offer better incremental compilation performance. When modifying type definitions, TypeScript only needs to recheck affected interfaces, whereas type aliases may trigger more extensive rechecks.
Type Compatibility
In the type system, interfaces and type aliases follow essentially the same compatibility rules:
interface IUser {
name: string;
}
type TUser = {
name: string;
};
let iUser: IUser = { name: 'Alice' };
let tUser: TUser = { name: 'Bob' };
iUser = tUser; // Compatible
tUser = iUser; // Compatible
Type Extension Patterns
This pattern is common in open-source library type definitions:
// Core library type
interface Request {
url: string;
}
// User extension
interface Request {
method?: string;
}
Type aliases cannot achieve this extension pattern but can simulate it via intersection types:
type Request = {
url: string;
} & {
method?: string;
};
Type Operators
Type aliases can work with more type operators:
type Nullable<T> = T | null;
type NonNullable<T> = T extends null | undefined ? never : T;
Practical Examples of Type Inference
In React components, type aliases can simplify props type extraction:
type PropsOf<T> = T extends React.ComponentType<infer P> ? P : never;
type MyComponentProps = PropsOf<typeof MyComponent>;
Type and Value Namespaces
In TypeScript, types and values exist in different namespaces. Interface-created type names exist in both the type and value spaces, while type alias-created names exist only in the type space.
interface I {
x: number;
}
class C implements I {
x = 0;
}
type T = {
x: number;
};
class D implements T {
x = 0;
}
Type Parameter Defaults
Type aliases support more flexible type parameter defaults:
type Container<T = string> = { value: T };
Interfaces also support type parameter defaults:
interface Container<T = string> {
value: T;
}
Type Predicates
Both behave the same in custom type guards:
interface Cat {
meow(): void;
}
type Dog = {
bark(): void;
};
function isCat(pet: Cat | Dog): pet is Cat {
return (pet as Cat).meow !== undefined;
}
Flexibility in Type Extension
Type aliases can implement more complex extension logic via conditional types:
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
Mutual References Between Types and Interfaces
Type aliases and interfaces can reference each other:
interface Shape {
type: 'circle' | 'square';
radius?: number;
sideLength?: number;
}
type Circle = Shape & {
type: 'circle';
radius: number;
};
Depth in Type Queries
Type aliases handle deep type queries better:
type User = {
name: string;
address: {
street: string;
geo: [number, number];
};
};
type Geo = User['address']['geo']; // [number, number]
Type Operations
Type aliases support richer type operations:
type A = { a: number };
type B = { b: string };
type C = A & B; // { a: number; b: string }
type D = keyof C; // 'a' | 'b'
Selection Strategy for Types and Interfaces
- Prefer interfaces for public API definitions to facilitate user extension.
- Use type aliases for component Props and State, especially when union types are needed.
- Type aliases' intersection types are more intuitive when combining multiple types.
- Interfaces are mandatory when declaration merging is required.
- Complex type operations must use type aliases.
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn