TypeScript 类型系统全面指南:从基础到高级应用
一、TypeScript 基础类型详解
1.1 原始数据类型
1.1.1 数字类型(number)
在 TypeScript 中,number 类型表示所有数字,包括整数和浮点数,没有专门的整数类型。这与 JavaScript 中的数字类型一致。
基本使用:
let age: number = 30;
let temperature: number = 36.5;
let binaryNumber: number = 0b1010; // 二进制数10
let octalNumber: number = 0o744; // 八进制数484
let hexNumber: number = 0xf00d; // 十六进制数61453
let bigNumber: bigint = 100n; // 大整数类型注意:TypeScript 和 JavaScript 一样,不区分整数和浮点数,统一使用 number 类型表示所有数值。
1.1.2 字符串类型(string)
string 类型用于表示文本数据,可以使用单引号、双引号或反引号(模板字符串)定义。
基本使用:
let name: string = "Alice";
let greeting: string = "Hello, TypeScript!";
let multiLine: string = `这是一个
多行字符串`;模板字符串: TypeScript 支持模板字符串,允许在字符串中嵌入表达式,使用${}语法:
let name: string = "Alice";
let age: number = 30;
let message: string = `我的名字是${name},今年${age + 1}岁。`;
console.log(message); // 输出:我的名字是Alice,今年31岁。1.1.3 布尔类型(boolean)
boolean 类型表示逻辑值,只有两个可能的值:true 和 false。
基本使用:
let isDone: boolean = false;
let hasPermission: boolean = true;1.1.4 null 和 undefined
null 和 undefined 在 TypeScript 中分别表示空值和未定义值。
基本使用:
let empty: null = null;
let uninitialized: undefined = undefined;严格空值检查: 在 TypeScript 中启用严格空值检查(--strictNullChecks)后,null 和 undefined 只能赋值给它们各自的类型或 void 类型:
// 启用严格空值检查
let x: number;
x = 1; // 正确
x = null; // 错误:不能将null赋值给number类型
x = undefined; // 错误:不能将undefined赋值给number类型如果需要允许变量接受 null 或 undefined,可以使用联合类型:
let maybeNumber: number | null | undefined;
maybeNumber = 1; // 正确
maybeNumber = null; // 正确
maybeNumber = undefined; // 正确1.1.5 void 类型
void 类型表示没有值,通常用于函数返回值类型,表示该函数不返回任何值。
基本使用:
function logMessage(message: string): void {
console.log(message);
}声明一个 void 类型的变量只能赋值 undefined 或 null(在非严格模式下):
let unusable: void = undefined;
// 在严格模式下,void类型变量只能赋值undefined
// 在非严格模式下,可以赋值null1.1.6 never 类型
never 类型表示永远不会出现的值,通常用于函数返回值类型,表示该函数永远不会正常返回(例如抛出异常或无限循环)。
基本使用:
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}never 类型是所有类型的子类型,可以赋值给任何类型,但没有类型可以赋值给 never 类型(除了 never 本身):
let x: never;
let y: number;
x = 123; // 错误:不能将number类型赋值给never类型
y = throwError("出错了"); // 正确:never类型可以赋值给number类型1.2 复合类型
1.2.1 数组类型
数组类型表示相同类型元素的有序集合。
基本语法:
// 元素类型后面加方括号
let numbers: number[] = [1, 2, 3];
// 使用泛型Array类型
let names: Array<string> = ["Alice", "Bob"];多维数组:
let matrix: number[][] = [
[1, 2],
[3, 4],
];1.2.2 元组类型(tuple)
元组类型是 TypeScript 特有的类型,用于表示已知元素数量和类型的数组,每个元素可以是不同的类型。
基本使用:
let person: [string, number] = ["Alice", 30];可以通过索引访问元组元素,但必须符合定义的类型:
let name: string = person[0]; // 正确
let age: number = person[1]; // 正确
// person[2] = "test"; // 错误:索引2超出元组长度也可以解构元组:
let [username, userAge] = person;
console.log(username); // Alice
console.log(userAge); // 301.2.3 对象类型
对象类型用于描述具有特定属性和方法的对象结构。
基本语法:
let person: { name: string; age: number } = {
name: "Alice",
age: 30,
};也可以使用接口定义对象类型:
interface Person {
name: string;
age: number;
}
let person: Person = {
name: "Alice",
age: 30,
};可选属性: 使用问号?表示可选属性:
interface Person {
name: string;
age?: number;
}
let person: Person = {
name: "Alice",
// age属性可选,可以省略
};只读属性: 使用 readonly 关键字表示只读属性,只能在对象初始化时赋值:
interface Person {
readonly id: number;
name: string;
}
let person: Person = {
id: 1,
name: "Alice",
};
// person.id = 2; // 错误:不能修改只读属性1.2.4 枚举类型(enum)
枚举类型用于定义一组命名常量,使代码更易读和维护。
基本使用:
enum Color {
Red,
Green,
Blue,
}
let favoriteColor: Color = Color.Green;
console.log(favoriteColor); // 输出1(默认从0开始编号)手动赋值: 可以手动为枚举成员赋值:
enum Color {
Red = 1,
Green,
Blue,
}
let favoriteColor: Color = Color.Green;
console.log(favoriteColor); // 输出2(自动递增)也可以为每个成员显式赋值:
enum Color {
Red = 1,
Green = 2,
Blue = 4,
}字符串枚举: 枚举成员也可以是字符串类型:
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}1.3 特殊类型
1.3.1 any 类型
any 类型表示可以是任意类型的值,绕过 TypeScript 的类型检查。
基本使用:
let value: any = 42;
value = "hello"; // 允许,any类型可以动态改变类型
value.foo(); // 允许,即使foo属性不存在使用场景:
- 变量值会动态改变时(如用户输入)
- 改写现有代码时,暂时绕过类型检查
- 定义存储各种类型数据的数组时
注意:过度使用 any 类型会降低 TypeScript 的类型安全优势,应谨慎使用。
1.3.2 unknown 类型
unknown 类型与 any 类似,但更安全,必须经过类型检查后才能赋值给其他类型变量。
基本使用:
let value: unknown = 42;
value = "hello"; // 允许
let num: number;
num = value; // 错误:不能将unknown类型直接赋值给number类型
if (typeof value === "number") {
num = value; // 正确:经过类型检查后可以赋值
}类型检查: 使用类型断言或类型保护来检查 unknown 类型的值:
function getSafeValue(value: unknown): string {
if (typeof value === "string") {
return value;
}
throw new Error("值不是字符串类型");
}1.3.3 字面量类型
字面量类型允许变量只能取特定的字面量值。
基本使用:
let direction: "up" | "down" | "left" | "right" = "up";
direction = "down"; // 正确
// direction = "middle"; // 错误:middle不在允许的字面量集合中与联合类型结合:
type Booleanish = true | false | "yes" | "no";
let flag: Booleanish = "yes";1.3.4 类型断言
类型断言用于告诉编译器变量的实际类型,当编译器无法自动推断类型时使用。
基本语法:
// 使用as语法
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;
// 使用尖括号语法(在JSX中不可用)
let strLength: number = (<string>someValue).length;注意:类型断言只是告诉编译器变量的类型,不会实际检查值的类型,过度使用可能导致运行时错误。
二、TypeScript 高级类型
2.1 联合类型与交叉类型
2.1.1 联合类型(Union Types)
联合类型表示一个值可以是多种类型之一,使用|符号分隔。
基本使用:
let id: string | number;
id = "123"; // 正确
id = 456; // 正确访问联合类型的属性: 只能访问联合类型中所有类型共有的属性:
function printId(id: string | number) {
// console.log(id.length); // 错误:number类型没有length属性
console.log(id.toString()); // 正确:所有类型都有toString方法
}类型保护: 使用类型保护来缩小联合类型的范围:
function printId(id: string | number) {
if (typeof id === "string") {
console.log(id.length); // 正确:此时id被类型保护为string类型
} else {
console.log(id); // 正确:此时id被类型保护为number类型
}
}2.1.2 交叉类型(Intersection Types)
交叉类型表示一个值同时具有多种类型的特性,使用&符号分隔。
基本使用:
interface Person {
name: string;
}
interface Employee {
company: string;
}
type Manager = Person & Employee;
let manager: Manager = {
name: "Alice",
company: "ABC Corp",
};应用场景:
- 组合多个接口的特性
- 扩展现有类型
- 创建混合类型
2.2 类型别名与接口
2.2.1 类型别名(Type Aliases)
类型别名用于为现有类型创建一个新名字,增加代码可读性。
基本语法:
type Name = string;
type Age = number;
type UserId = string | number;
type Point = { x: number; y: number };区别于接口: 类型别名可以用于原始类型、联合类型、元组等,而接口只能描述对象结构。
2.2.2 接口(Interfaces)
接口用于定义对象的结构,描述对象的属性和方法。
基本语法:
interface Person {
name: string;
age: number;
sayHello(): void;
}
let person: Person = {
name: "Alice",
age: 30,
sayHello() {
console.log("Hello!");
},
};可选属性: 使用问号?表示可选属性:
interface Person {
name: string;
age?: number;
}只读属性: 使用 readonly 关键字表示只读属性:
interface Person {
readonly id: number;
name: string;
}函数类型: 接口可以描述函数类型:
interface Adder {
(a: number, b: number): number;
}
let add: Adder = function (a, b) {
return a + b;
};2.2.3 类型别名与接口的区别
| 特性 | 类型别名 | 接口 |
|---|---|---|
| 定义方式 | type 关键字 | interface 关键字 |
| 可扩展性 | 不可扩展(但可以重新定义) | 可扩展(使用 extends) |
| 适用范围 | 所有类型(原始类型、联合类型、元组等) | 仅对象结构 |
| 重复定义 | 会合并(如果结构兼容) | 会合并(自动合并同名接口) |
何时使用:
- 描述对象结构时优先使用接口
- 需要定义联合类型、元组或其他复合类型时使用类型别名
2.3 泛型
2.3.1 泛型基础
泛型是 TypeScript 中最强大的特性之一,允许创建可以适用于多种类型的组件,提高代码复用性和类型安全性。
基本语法:
// 泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数
let output1 = identity<string>("myString"); // 显式指定类型参数
let output2 = identity(5); // 类型推断自动推断类型为number泛型接口:
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;泛型类:
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};2.3.2 泛型约束
泛型约束用于限制类型变量的类型范围,而不是接受任何可能的类型。
基本语法:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // 现在可以安全访问length属性
return arg;
}
loggingIdentity("hello"); // 正确:string有length属性
// loggingIdentity(5); // 错误:number没有length属性多个类型参数约束:
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = source[id] as any;
}
return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 }); // x将变为{ a:1, b:10, c:3, d:20 }2.3.3 泛型高级应用
泛型工具类型: TypeScript 提供了多个内置泛型工具类型,如 Partial、Readonly、Pick 等:
// Partial<T>:将T的所有属性变为可选
type PartialUser = Partial<User>;
// Readonly<T>:将T的所有属性变为只读
type ReadonlyUser = Readonly<User>;
// Pick<T, K>:从T中选取属性K组成新类型
type NameOnlyUser = Pick<User, "name">;泛型条件类型:
type TypeName<T> = T extends string
? "string"
: T extends number
? "number"
: T extends boolean
? "boolean"
: T extends undefined
? "undefined"
: T extends Function
? "function"
: "object";
type T1 = TypeName<string>; // "string"
type T2 = TypeName<"a">; // "string"
type T3 = TypeName<() => void>; // "function"
type T4 = TypeName<string[]>; // "object"2.4 条件类型
2.4.1 基本条件类型
条件类型允许在类型级别进行条件判断,语法类似于 JavaScript 的三元运算符。
基本语法:
T extends U ? X : Y当 T 可以赋值给 U 时,结果为 X 类型;否则为 Y 类型。
示例:
interface IdLabel {
id: number;
}
interface NameLabel {
name: string;
}
type NameOrId<T extends number | string> = T extends number
? IdLabel
: NameLabel;
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
throw "未实现";
}
let a = createLabel("typescript"); // a的类型是NameLabel
let b = createLabel(2.8); // b的类型是IdLabel
let c = createLabel(Math.random() ? "hello" : 42); // c的类型是NameLabel | IdLabel2.4.2 infer 关键字
infer 关键字用于在条件类型中提取类型的某一部分信息。
基本语法:
T extends (...args: any) => infer R ? R : any示例:
// 获取函数返回值类型
type ReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: any;
type Func = (a: string, b: number) => boolean;
type Result = ReturnType<Func>; // boolean
// 获取函数参数类型
type Parameters<T extends (...args: any) => any> = T extends (
...args: infer P
) => any
? P
: never;
type FuncParams = Parameters<Func>; // [string, number]
// 提取数组元素类型
type ElementType<T> = T extends (infer U)[] ? U : T;
type StrArr = ElementType<string[]>; // string
type NumOrStrArr = ElementType<string[] | number[]>; // string | number2.4.3 分布式条件类型
当条件类型作用于泛型类型且给定联合类型时,会自动对联合类型的每个成员应用条件类型,这种现象称为分布式条件类型。
示例:
type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>;
// 等同于 string[] | number[]阻止分发: 在某些情况下,我们可能希望阻止条件类型的自动分发,可以通过以下方法实现:
// 元组包装法
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
// 函数包装法
type ToArrayNonDist<Type> = ((arg: Type) => void) extends (arg: any) => void
? Type[]
: never;
// 交叉类型{}法
type ToArrayNonDist<Type> = Type & {} extends any ? Type[] : never;2.5 映射类型
2.5.1 基本映射类型
映射类型允许基于现有类型创建新类型,通过遍历现有类型的键并对每个键应用转换。
基本语法:
{ [Property in keyof Type]: NewType }示例:
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
type Features = {
darkMode: () => void;
newUserProfile: () => void;
};
type FeatureOptions = OptionsFlags<Features>;
// 结果为:
// type FeatureOptions = {
// darkMode: boolean;
// newUserProfile: boolean;
// }2.5.2 映射修饰符
映射类型支持添加或移除属性的 readonly 和?修饰符。
添加修饰符: 使用+前缀(可以省略)添加修饰符:
type CreateMutable<Type> = {
+readonly [Property in keyof Type]: Type[Property];
};移除修饰符: 使用-前缀移除修饰符:
type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};
type LockedAccount = {
readonly id: string;
readonly name: string;
};
type UnlockedAccount = CreateMutable<LockedAccount>;
// 结果为:
// type UnlockedAccount = {
// id: string;
// name: string;
// }移除可选属性:
type Concrete<Type> = {
[Property in keyof Type]-?: Type[Property];
};
type MaybeUser = {
id: string;
name?: string;
age?: number;
};
type User = Concrete<MaybeUser>;
// 结果为:
// type User = {
// id: string;
// name: string;
// age: number;
// }2.5.3 键重映射
TypeScript 4.1 及以上版本支持在映射类型中使用 as 子句进行键重映射。
基本语法:
{ [Property in keyof Type as NewKey]: NewType }示例:
type Getters<Type> = {
[Property in keyof Type as `get${Capitalize<
string & Property
>}`]: () => Type[Property];
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
// 结果为:
// type LazyPerson = {
// getName: () => string;
// getAge: () => number;
// getLocation: () => string;
// }2.5.4 属性过滤
映射类型可以结合条件类型过滤特定属性。
示例:
type Filter<T> = {
[K in keyof T as T[K] extends string ? K : never]: string;
};
type User = { name: string; age: number; email: string };
type FilteredUser = Filter<User>; // { name: string; email: string }2.5.5 内置映射工具类型
TypeScript 提供了多个内置的映射工具类型,方便进行常见的类型转换:
| 工具类型 | 描述 |
|---|---|
Partial<T> | 使 T 的所有属性变为可选 |
Required<T> | 移除 T 的所有可选属性 |
Readonly<T> | 使 T 的所有属性变为只读 |
Pick<T, K> | 从 T 中选取属性 K 组成新类型 |
Omit<T, K> | 从 T 中排除属性 K 组成新类型 |
Record<K, T> | 创建以 K 为键、T 为值的对象类型 |
Exclude<T, U> | 从 T 中排除 U 中存在的类型 |
Extract<T, U> | 提取 T 中 U 中存在的类型 |
NonNullable<T> | 从 T 中排除 null 和 undefined |
ReturnType<T> | 获取函数 T 的返回值类型 |
InstanceType<T> | 获取构造函数 T 的实例类型 |
三、函数类型与参数类型
3.1 函数类型定义
3.1.1 函数声明与表达式
函数声明:
function add(a: number, b: number): number {
return a + b;
}函数表达式:
let add: (a: number, b: number) => number = function (a, b) {
return a + b;
};箭头函数:
let add: (a: number, b: number) => number = (a, b) => a + b;3.1.2 函数类型接口
使用接口定义函数类型:
interface Adder {
(a: number, b: number): number;
}
let add: Adder = function (a, b) {
return a + b;
};3.1.3 函数返回值类型
无返回值函数:
function log(message: string): void {
console.log(message);
}返回对象类型:
function createUser(name: string, age: number): { name: string; age: number } {
return { name, age };
}返回联合类型:
function getValue(key: string): string | number | undefined {
// 根据key返回不同类型的值
}3.2 函数参数类型
3.2.1 必选参数
基本语法:
function greet(name: string) {
console.log(`Hello, ${name}`);
}
greet("Alice"); // 正确
// greet(); // 错误:缺少必选参数name3.2.2 可选参数
使用问号?表示可选参数:
function greet(name: string, message?: string) {
console.log(message ? `${message}, ${name}` : `Hello, ${name}`);
}
greet("Alice"); // 正确
greet("Alice", "Hi"); // 正确3.2.3 默认参数
为参数提供默认值:
function greet(name: string, message: string = "Hello") {
console.log(`${message}, ${name}`);
}
greet("Alice"); // 正确,使用默认message
greet("Alice", "Hi"); // 正确,覆盖默认message3.2.4 剩余参数
使用...语法表示剩余参数:
function sum(...numbers: number[]): number {
return numbers.reduce((acc, curr) => acc + curr, 0);
}
sum(1, 2, 3); // 正确,参数为[1, 2, 3]剩余参数的类型是数组类型,必须是函数参数列表中的最后一个参数。
3.2.5 参数解构
使用对象解构和数组解构来定义函数参数:
对象解构:
function printUser({ name, age }: { name: string; age: number }) {
console.log(`Name: ${name}, Age: ${age}`);
}
printUser({ name: "Alice", age: 30 });数组解构:
function printNumbers([first, second]: [number, number]) {
console.log(`First: ${first}, Second: ${second}`);
}
printNumbers([1, 2]);3.2.6 参数类型约束
使用泛型约束来限制函数参数的类型:
function logIdentity<T extends { length: number }>(arg: T): T {
console.log(arg.length); // 安全访问length属性
return arg;
}
logIdentity("hello"); // 正确
logIdentity([1, 2, 3]); // 正确
// logIdentity(123); // 错误:number类型没有length属性3.3 函数重载
3.3.1 函数重载声明
函数重载允许定义多个同名函数,但参数列表不同。
基本语法:
// 重载声明
function add(a: number, b: number): number;
function add(a: string, b: string): string;
// 实现
function add(a: any, b: any): any {
return a + b;
}
// 使用
let numResult = add(1, 2); // 类型为number
let strResult = add("Hello, ", "world!"); // 类型为string重载解析: TypeScript 会根据调用时的参数类型选择最合适的重载:
// 重载声明
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
// 实现
function makeDate(mOrTimestamp: any, d?: any, y?: any): Date {
if (typeof mOrTimestamp === "number") {
return new Date(mOrTimestamp);
} else {
return new Date(y, mOrTimestamp - 1, d);
}
}
// 使用
let d1 = makeDate(1234567890); // 正确:调用第一个重载
let d2 = makeDate(5, 5, 2020); // 正确:调用第二个重载3.3.2 重载与泛型的选择
在某些情况下,泛型可能比重载更简洁:
使用重载:
function identity(arg: number): number;
function identity(arg: string): string;
function identity(arg: any): any {
return arg;
}使用泛型:
function identity<T>(arg: T): T {
return arg;
}何时选择:
- 当需要不同的返回类型基于参数类型时使用重载
- 当函数逻辑对所有类型都相同时使用泛型
四、类与接口类型
4.1 类的类型定义
4.1.1 类的基本类型定义
类的类型定义包括实例属性、实例方法、构造函数和静态成员。
基本语法:
class Person {
// 实例属性
name: string;
age: number;
// 构造函数
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// 实例方法
greet(): void {
console.log(
`Hello, my name is ${this.name} and I'm ${this.age} years old.`
);
}
}
// 创建实例
let person: Person = new Person("Alice", 30);
person.greet(); // 输出:Hello, my name is Alice and I'm 30 years old.4.1.2 类的继承与类型
使用 extends 关键字实现类的继承:
class Employee extends Person {
company: string;
constructor(name: string, age: number, company: string) {
super(name, age); // 调用父类构造函数
this.company = company;
}
// 重写父类方法
greet(): void {
super.greet(); // 调用父类方法
console.log(`I work at ${this.company}.`);
}
}
let employee: Employee = new Employee("Bob", 35, "ABC Corp");
employee.greet();类型兼容性: 子类实例可以赋值给父类类型变量:
let person: Person = new Employee("Bob", 35, "ABC Corp");4.1.3 类的访问修饰符
TypeScript 支持三种访问修饰符:
| 修饰符 | 描述 |
|---|---|
| public | 公共(默认),可以在任何地方访问 |
| private | 私有,只能在类内部访问 |
| protected | 受保护,只能在类内部及其子类中访问 |
使用示例:
class Person {
public name: string;
private secret: string;
protected age: number;
constructor(name: string, age: number) {
this.name = name;
this.secret = "secret";
this.age = age;
}
protected greet(): void {
console.log(`Hello, my name is ${this.name}.`);
}
}
class Employee extends Person {
constructor(name: string, age: number) {
super(name, age);
// this.secret = "new secret"; // 错误:不能访问私有属性
this.age = 30; // 正确:可以访问受保护属性
}
public introduce(): void {
this.greet(); // 正确:可以调用受保护方法
}
}4.1.4 类的静态成员
静态成员属于类本身,而不是类的实例,可以通过类名直接访问。
使用示例:
class MathUtils {
static PI: number = 3.1415926;
static calculateCircleArea(radius: number): number {
return this.PI * radius * radius;
}
}
console.log(MathUtils.PI); // 正确:直接通过类名访问静态属性
console.log(MathUtils.calculateCircleArea(5)); // 正确:调用静态方法4.2 类与接口的关系
4.2.1 类实现接口
类可以实现一个或多个接口,确保类符合接口定义的结构。
基本语法:
interface Drawable {
draw(): void;
}
class Circle implements Drawable {
radius: number;
constructor(radius: number) {
this.radius = radius;
}
draw(): void {
console.log(`Drawing a circle with radius ${this.radius}`);
}
}
let shape: Drawable = new Circle(5);
shape.draw(); // 输出:Drawing a circle with radius 5实现多个接口:
interface Printable {
print(): void;
}
class Circle implements Drawable, Printable {
// 实现两个接口的方法
}4.2.2 接口继承类
接口可以继承类,继承类的成员(包括私有和受保护成员)。
示例:
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select(): void {
// 可以访问父类的私有成员state吗?
// 不可以,因为SelectableControl接口继承自Control,但实现类Button无法访问Control的私有成员
}
}注意:当接口继承类时,接口会继承类的成员,但类的私有和受保护成员会被视为接口的私有和受保护成员,这可能导致实现类无法满足接口的要求。
4.2.3 抽象类
抽象类是不能直接实例化的类,通常作为其他类的基类,包含抽象方法和具体方法。
基本语法:
abstract class Animal {
abstract makeSound(): void; // 抽象方法,必须在子类中实现
move(): void {
// 具体方法
console.log("Moving along!");
}
}
class Dog extends Animal {
makeSound(): void {
console.log("Woof!");
}
}
// let animal: Animal = new Animal(); // 错误:不能实例化抽象类
let dog: Animal = new Dog();
dog.makeSound(); // 正确:调用子类实现的方法
dog.move(); // 正确:调用父类的具体方法4.3 类属性类型
4.3.1 实例属性类型
实例属性属于类的每个实例,可以是任何类型。
基本语法:
class Person {
name: string;
age: number;
hobbies?: string[]; // 可选属性
constructor(name: string, age: number, hobbies?: string[]) {
this.name = name;
this.age = age;
this.hobbies = hobbies;
}
}类型修饰符: 可以使用 readonly 修饰符将属性声明为只读:
class Person {
readonly id: number;
name: string;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
}
let person = new Person(1, "Alice");
// person.id = 2; // 错误:不能修改只读属性4.3.2 静态属性类型
静态属性属于类本身,而不是实例,可以通过类名直接访问。
基本语法:
class MathUtils {
static PI: number = 3.1415926;
static version: string = "1.0.0";
}
console.log(MathUtils.PI); // 正确:直接通过类名访问静态属性4.3.3 索引签名
索引签名允许类的实例像数组或对象一样通过索引访问。
基本语法:
class Dictionary {
[key: string]: any; // 字符串索引签名
}
let dict = new Dictionary();
dict["name"] = "Alice";
dict["age"] = 30;
console.log(dict["name"]); // 输出:Alice类型限制:
class NumberDictionary {
[key: string]: number;
constructor(public name: string, public age: number) {}
}
let dict = new NumberDictionary("Alice", 30);
dict["score"] = 90; // 正确
// dict["name"] = "Bob"; // 错误:name属性是字符串类型,与索引签名的number类型冲突4.3.4 类的类型别名
可以使用类型别名来简化类的类型定义:
type PersonType = typeof Person;
function createInstance<T extends { new (...args: any[]): any }>(
ctor: T,
...args: any[]
): InstanceType<T> {
return new ctor(...args);
}
let person: Person = createInstance(Person, "Alice", 30);五、应用场景与最佳实践
5.1 函数参数类型应用场景
5.1.1 函数参数类型的最佳实践
明确参数类型:
// 好的实践:明确参数类型和返回值类型
function add(a: number, b: number): number {
return a + b;
}
// 不好的实践:使用any类型,失去类型安全
function add(a: any, b: any): any {
return a + b;
}避免过度指定类型:
// 好的实践:使用更通用的类型
function print(value: any) {
console.log(value);
}
// 不好的实践:过度指定类型,限制使用场景
function print(value: string | number) {
console.log(value);
}使用联合类型处理多种情况:
function format(value: string | number): string {
if (typeof value === "string") {
return `"${value}"`;
} else {
return value.toString();
}
}使用可选参数和默认参数:
// 可选参数
function greet(name: string, message?: string) {
console.log(message ? `${message}, ${name}` : `Hello, ${name}`);
}
// 默认参数
function greet(name: string, message: string = "Hello") {
console.log(`${message}, ${name}`);
}5.1.2 回调函数参数类型
回调函数类型定义:
function processData(data: any[], callback: (item: any) => void) {
data.forEach(callback);
}
processData([1, 2, 3], function (item) {
console.log(item);
});带返回值的回调函数:
function transformData(data: any[], transformer: (item: any) => any): any[] {
return data.map(transformer);
}
let doubled = transformData([1, 2, 3], function (item) {
return item * 2;
});使用泛型增强回调函数:
function processData<T, U>(data: T[], callback: (item: T) => U): U[] {
return data.map(callback);
}
let doubled: number[] = processData([1, 2, 3], function (item) {
return item * 2;
});5.1.3 函数参数验证
使用类型断言进行参数验证:
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new Error("Value is not a string");
}
}
function processString(value: unknown) {
assertIsString(value);
// 现在可以安全地将value视为string类型
console.log(value.toUpperCase());
}使用类型谓词:
function isString(value: unknown): value is string {
return typeof value === "string";
}
function processString(value: unknown) {
if (isString(value)) {
// 现在可以安全地将value视为string类型
console.log(value.toUpperCase());
} else {
throw new Error("Value is not a string");
}
}5.2 类属性类型应用场景
5.2.1 类属性类型的最佳实践
明确属性类型:
// 好的实践:明确属性类型
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
// 不好的实践:使用any类型,失去类型安全
class Person {
name: any;
age: any;
constructor(name, age) {
this.name = name;
this.age = age;
}
}使用可选属性:
class Person {
name: string;
age?: number; // 可选属性
constructor(name: string, age?: number) {
this.name = name;
this.age = age;
}
}使用只读属性:
class Person {
readonly id: number;
name: string;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
}5.2.2 类属性的初始化与验证
属性初始化:
class Person {
name: string = "Unknown"; // 直接初始化
age: number;
constructor(age: number) {
this.age = age; // 在构造函数中初始化
}
}属性验证:
class Person {
private _age: number;
get age(): number {
return this._age;
}
set age(value: number) {
if (value < 0) {
throw new Error("Age cannot be negative");
}
this._age = value;
}
}
let person = new Person();
person.age = 30; // 正确
// person.age = -5; // 错误:抛出异常5.2.3 类的继承与属性类型
子类属性类型:
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
}
class Dog extends Animal {
breed: string; // 子类新增属性
constructor(name: string, breed: string) {
super(name); // 调用父类构造函数
this.breed = breed;
}
}重写父类属性:
class Animal {
color: string = "black"; // 父类属性
makeSound(): void {
console.log("...");
}
}
class Dog extends Animal {
color: string = "brown"; // 重写父类属性
makeSound(): void {
console.log("Woof!"); // 重写父类方法
}
}5.3 类型安全的异步编程
5.3.1 Promise 类型定义
Promise 类型语法:
function fetchData(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Data fetched successfully");
}, 1000);
});
}使用 async/await:
async function processData() {
try {
let data = await fetchData();
console.log(data);
} catch (error) {
console.error("Error:", error);
}
}5.3.2 类型安全的回调函数
使用泛型回调函数:
function delay<T>(value: T, timeout: number): Promise<T> {
return new Promise((resolve) => {
setTimeout(() => resolve(value), timeout);
});
}
delay("Hello", 1000).then((data: string) => {
console.log(data); // 类型安全
});使用类型断言:
function processData(callback: (data: any) => void) {
// 模拟异步数据获取
setTimeout(() => {
callback("Data from server");
}, 1000);
}
processData((data: string) => {
console.log(data); // 类型安全
});5.3.3 异步函数的错误处理
错误处理最佳实践:
async function fetchData(): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve("Success") : reject("Failure");
}, 1000);
});
}
async function processData() {
try {
let data = await fetchData();
console.log("Data:", data);
} catch (error) {
console.error("Error:", error);
}
}使用 try...catch 包裹 Promise 链:
function fetchData(): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve("Success") : reject("Failure");
}, 1000);
});
}
fetchData()
.then((data) => {
console.log("Data:", data);
})
.catch((error) => {
console.error("Error:", error);
});5.4 大型项目中的类型设计模式
5.4.1 类型安全的状态管理
使用接口定义状态结构:
interface UserState {
users: User[];
loading: boolean;
error: string | null;
}
let initialState: UserState = {
users: [],
loading: false,
error: null,
};使用泛型定义状态管理工具:
class StateManager<T> {
private state: T;
constructor(initialState: T) {
this.state = initialState;
}
getState(): T {
return this.state;
}
updateState(newState: Partial<T>): void {
this.state = { ...this.state, ...newState };
}
}
let userStateManager = new StateManager(initialState);
userStateManager.updateState({ loading: true });5.4.2 类型安全的配置管理
使用接口定义配置结构:
interface AppConfig {
apiUrl: string;
timeout: number;
debug: boolean;
}
let config: AppConfig = {
apiUrl: "https://api.example.com",
timeout: 5000,
debug: false,
};配置验证:
function validateConfig(config: Partial<AppConfig>): AppConfig {
const defaultConfig: AppConfig = {
apiUrl: "https://api.example.com",
timeout: 5000,
debug: false,
};
return { ...defaultConfig, ...config };
}
let userConfig = validateConfig({ timeout: 3000 });
console.log(userConfig); // 输出:{ apiUrl: "https://api.example.com", timeout: 3000, debug: false }5.4.3 类型安全的依赖注入
使用接口定义依赖:
interface Logger {
log(message: string): void;
error(message: string): void;
}
class ConsoleLogger implements Logger {
log(message: string): void {
console.log(message);
}
error(message: string): void {
console.error(message);
}
}
class Service {
private logger: Logger;
constructor(logger: Logger) {
this.logger = logger;
}
doSomething(): void {
this.logger.log("Doing something...");
// ...
}
}
// 使用依赖注入
let service = new Service(new ConsoleLogger());依赖注入容器:
class Container {
private services = new Map<string, any>();
register<T>(key: string, implementation: new () => T): void {
this.services.set(key, implementation);
}
resolve<T>(key: string): T {
let implementation = this.services.get(key);
if (!implementation) {
throw new Error(`Service ${key} not registered`);
}
return new implementation();
}
}
// 注册服务
let container = new Container();
container.register<Logger>("Logger", ConsoleLogger);
container.register<Service>("Service", Service);
// 解析服务
let service = container.resolve<Service>("Service");5.5 类型系统的最佳实践
5.5.1 类型别名与接口的选择
选择接口的情况:
- 描述对象结构时
- 需要类型扩展时
- 需要与其他接口组合时
选择类型别名的情况:
- 定义联合类型时
- 定义元组类型时
- 定义原始类型别名时
- 需要描述函数类型时
示例:
// 好的实践:使用接口描述对象结构
interface Point {
x: number;
y: number;
}
// 好的实践:使用类型别名定义联合类型
type MaybeNumber = number | null | undefined;5.5.2 避免过度使用类型断言
好的实践:
function getSafeValue(value: unknown): string {
if (typeof value === "string") {
return value;
}
throw new Error("Value is not a string");
}不好的实践:
function getValue(value: unknown): string {
return value as string; // 不进行类型检查的类型断言
}5.5.3 使用严格模式
启用 TypeScript 的严格模式(--strict)是最佳实践,它会启用一系列严格的类型检查选项。
严格模式选项:
- --strictNullChecks:严格的空值检查
- --noImplicitAny:禁止隐式的 any 类型
- --strictFunctionTypes:严格的函数类型检查
- --strictBindCallApply:严格的 bind/call/apply 检查
- --strictPropertyInitialization:严格的属性初始化检查
- --noImplicitThis:禁止隐式的 this 类型
示例: 在 tsconfig.json 中启用严格模式:
{
"compilerOptions": {
"strict": true
}
}六、总结与学习路径
6.1 核心知识点总结
基础类型:
- 原始类型:number、string、boolean、null、undefined、void、never
- 复合类型:数组、元组、对象、枚举
- 特殊类型:any、unknown、字面量类型
高级类型:
- 联合类型(|)和交叉类型(&)
- 类型别名和接口
- 泛型
- 条件类型
- 映射类型
函数类型:
- 参数类型:必选、可选、默认、剩余参数
- 函数重载
- 回调函数类型
类与接口:
- 类的属性和方法类型
- 类的继承和多态
- 接口实现和扩展
- 抽象类和抽象方法
最佳实践:
- 使用严格模式
- 明确类型定义
- 避免过度使用 any 类型
- 合理使用类型断言
- 使用泛型提高代码复用性
6.2 学习路径建议
入门阶段(1-2 周):
- 掌握基础类型和变量声明
- 学习函数类型和参数类型
- 理解接口和类的基本概念
- 实践简单的类型断言和联合类型
进阶阶段(2-4 周):
- 深入学习泛型和条件类型
- 掌握映射类型和工具类型
- 理解类的继承、多态和访问修饰符
- 实践类型安全的函数重载和回调函数
专家阶段(4 周以上):
- 深入理解类型推断和类型系统原理
- 掌握高级类型模式(如依赖注入、状态管理)
- 实践大型项目中的类型设计模式
- 探索 TypeScript 的高级特性(如装饰器、元编程)
6.3 学习资源推荐
官方文档:
- TypeScript 官方文档
- TypeScript Handbook
书籍:
- 《TypeScript 权威指南》
- 《Effective TypeScript》
- 《Pro TypeScript》
在线课程:
- TypeScript in 50 Shades of Red
- Advanced TypeScript
社区资源:
- TypeScript Deep Dive
- Awesome TypeScript
- TypeScript Weekly
实践项目:
- 参与开源 TypeScript 项目
- 使用 TypeScript 重构现有 JavaScript 项目
- 开发一个全栈 TypeScript 应用(前端 + 后端)
通过系统学习和不断实践,你将能够掌握 TypeScript 的核心概念和高级特性,写出更加健壮、可维护的代码,为成为前端架构师打下坚实的基础。记住,TypeScript 是一个渐进式的语言,可以根据项目需求逐步引入,不要期望一蹴而就,而是通过不断的实践和应用来加深理解。