TypeScript
一、TypeScript 基础
1. TypeScript 概述与优势
问题:TypeScript 与 JavaScript 的区别是什么?TypeScript 的优势有哪些?TypeScript 的类型系统特点是什么?TypeScript 的编译过程是怎样的?
答案:
TypeScript vs JavaScript 对比表:
| 特性 | TypeScript | JavaScript |
|---|---|---|
| 类型系统 | 静态类型,编译时类型检查 | 动态类型,运行时类型检查 |
| 错误检测 | 编译时报错,提前发现错误 | 运行时报错,调试困难 |
| 开发体验 | 智能提示、代码补全、重构支持 | 有限提示,依赖注释 |
| 兼容性 | JavaScript 的超集,完全兼容 | 原生支持所有环境 |
| 学习成本 | 需要学习类型语法 | 门槛较低 |
| 构建步骤 | 需要编译(tsc) | 可直接运行 |
| 项目规模 | 适合大型项目、团队协作 | 适合小型项目、快速原型 |
TypeScript 核心优势:
- 类型安全:编译时类型检查,减少运行时错误
- 代码可维护性:类型作为文档,提高代码可读性
- 开发效率:智能提示、代码补全、重构支持
- 渐进式采用:可将现有 JS 项目逐步迁移到 TS
- 增强的面向对象:类、接口、泛型等更完善的支持
类型系统特点:
- 静态类型:编译时进行类型检查
- 类型推断:自动推断变量类型,减少类型注解
- 结构化类型:基于形状(结构)进行类型检查,而非名义类型
- 类型兼容性:基于结构子类型,灵活性高
编译过程:
// 源码:app.ts
const greet = (name: string): string => {
return `Hello, ${name}!`;
};
// 编译步骤:
// 1. 词法分析:将源码分解为词法单元(tokens)
// 2. 语法分析:生成抽象语法树(AST)
// 3. 语义分析:类型检查、符号解析
// 4. 转换:将 TypeScript AST 转换为 JavaScript AST
// 5. 代码生成:生成 JavaScript 代码
// 6. 输出:app.js
const greet = (name) => {
return `Hello, ${name}!`;
};
编译命令:
# 编译单个文件
tsc app.ts
# 编译整个项目(使用 tsconfig.json)
tsc
# 监听模式,自动重新编译
tsc --watch
# 生成声明文件
tsc --declaration
# 严格模式
tsc --strict
补充说明:
- TypeScript 是 JavaScript 的超集,所有合法的 JavaScript 代码都是合法的 TypeScript 代码
- TypeScript 编译后会移除所有类型注解,生成纯 JavaScript 代码
- 可以使用
tsconfig.json配置文件来定制编译选项 - 对于小型项目,TypeScript 可能增加复杂性;对于大型项目,TypeScript 能显著提高代码质量
二、类型系统基础
2. 基本类型与类型注解
问题:TypeScript 的基本类型有哪些?TypeScript 类型注解的语法是什么?
答案:
TypeScript 基本类型:
| 类型 | 描述 | 示例 |
|---|---|---|
number | 数字(整数、浮点数) | let age: number = 25; |
string | 字符串 | let name: string = "Alice"; |
boolean | 布尔值 | let isDone: boolean = true; |
null | 空值 | let n: null = null; |
undefined | 未定义 | let u: undefined = undefined; |
symbol | 唯一标识符 | let sym: symbol = Symbol("key"); |
bigint | 大整数 | let big: bigint = 100n; |
any | 任意类型(禁用类型检查) | let anything: any = "whatever"; |
unknown | 未知类型(安全版 any) | let unsure: unknown = "something"; |
void | 无返回值 | function warn(): void { console.log("warn"); } |
never | 永不返回 | function error(message: string): never { throw new Error(message); } |
object | 非原始类型 | let obj: object = { x: 1, y: 2 }; |
array | 数组 | let list: number[] = [1, 2, 3]; |
tuple | 元组(固定长度数组) | let tuple: [string, number] = ["hello", 10]; |
enum | 枚举 | enum Color { Red, Green, Blue }; |
类型注解语法:
// 变量类型注解
let name: string = "Alice";
let age: number = 30;
let isStudent: boolean = true;
// 函数参数和返回值类型注解
function greet(name: string): string {
return `Hello, ${name}`;
}
// 对象类型注解
let person: { name: string; age: number } = {
name: "Bob",
age: 25
};
// 数组类型注解
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ["a", "b", "c"]; // 泛型语法
// 可选参数
function printName(firstName: string, lastName?: string): void {
console.log(`${firstName} ${lastName || ''}`);
}
// 默认参数(会自动推断类型)
function multiply(x: number, y: number = 10): number {
return x * y;
}
// 剩余参数
function sum(...numbers: number[]): number {
return numbers.reduce((acc, cur) => acc + cur, 0);
}
// 箭头函数类型注解
const add = (x: number, y: number): number => x + y;
类型推断:
// TypeScript 可以自动推断类型
let x = 10; // 推断为 number
let y = "hello"; // 推断为 string
let z = true; // 推断为 boolean
// 函数返回值类型推断
function square(x: number) {
return x * x; // 推断返回值为 number
}
// 对象属性类型推断
const user = {
name: "Alice", // 推断为 string
age: 25 // 推断为 number
};
补充说明:
- 类型注解是可选的,TypeScript 会尽可能进行类型推断
- 使用冒号
:进行类型注解,放在变量名、参数或返回值之后 - 优先使用类型推断,只在必要时添加类型注解
any类型会禁用类型检查,应尽量避免使用
三、接口与类型别名
3. 接口 vs 类型别名
问题:TypeScript 接口与类型的区别是什么?TypeScript 类型别名的作用是什么?
答案:
接口(interface):
- 用于定义对象形状(对象、类、函数的类型)
- 支持继承(extends)、实现(implements)
- 可重复声明,会自动合并
- 主要描述对象结构
类型别名(type):
- 为任意类型创建别名(不仅限于对象)
- 不支持继承,但可通过交叉类型(&)实现类似效果
- 不可重复声明
- 更灵活,支持联合类型、元组、基本类型等
对比表:
| 特性 | 接口(interface) | 类型别名(type) |
|---|---|---|
| 语法 | interface User { name: string } | type User = { name: string } |
| 扩展 | interface Admin extends User {} | type Admin = User & { role: string } |
| 实现 | 类可以实现接口 | 不能直接实现 |
| 合并 | 支持声明合并 | 不支持,会报错 |
| 适用场景 | 对象形状、类约束 | 任意类型别名、联合类型、复杂类型 |
| 性能 | 更好(早期版本差异) | 几乎相同(现代版本) |
代码示例:
// 1. 接口定义
interface Person {
name: string;
age: number;
}
// 接口继承
interface Employee extends Person {
employeeId: string;
department: string;
}
// 接口合并(声明合并)
interface User {
name: string;
}
interface User {
age: number;
}
// 合并结果:
// interface User {
// name: string;
// age: number;
// }
// 2. 类型别名定义
type Point = {
x: number;
y: number;
};
// 联合类型(接口无法实现)
type Status = "pending" | "success" | "error";
// 元组类型
type Tuple = [string, number];
// 交叉类型(实现类似继承)
type Animal = {
name: string;
};
type Dog = Animal & {
breed: string;
bark(): void;
};
// 3. 函数类型
interface MathFunc {
(x: number, y: number): number;
}
type MathFuncType = (x: number, y: number) => number;
// 两者都可以用于定义函数类型
const add: MathFunc = (a, b) => a + b;
const multiply: MathFuncType = (a, b) => a * b;
// 4. 类型别名支持更复杂的类型
type StringOrNumber = string | number;
type Nullable<T> = T | null;
type RecordType = Record<string, number>;
最佳实践:
- 优先使用接口:定义对象形状、类约束时
- 使用类型别名:定义联合类型、元组、复杂类型时
- 一致性:项目中选择一种风格并保持统一
- 何时选择:
- 需要声明合并 → 使用接口
- 需要扩展类型 → 两者都可,接口更直观
- 定义基本类型别名 → 使用类型别名
- 定义函数类型 → 两者都可,语法略有不同
补充说明:
- 现代的 TypeScript 中,接口和类型别名在性能上差异很小
- 声明合并是接口的重要特性,常用于扩展第三方库的类型定义
- 类型别名支持模板字面量类型、条件类型等高级特性
四、高级类型
4. 联合类型、交叉类型、字面量类型与枚举
问题:TypeScript 联合类型的使用?TypeScript 交叉类型的使用?TypeScript 字面量类型的使用?TypeScript 枚举的作用和使用场景?
答案:
联合类型(Union Types):
- 表示值可以是多种类型之一
- 使用竖线
|分隔类型
// 基本联合类型
type StringOrNumber = string | number;
function format(value: StringOrNumber): string {
if (typeof value === "string") {
return value.toUpperCase();
} else {
return value.toFixed(2);
}
}
// 联合类型与类型守卫
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rectangle"; width: number; height: number }
| { kind: "square"; size: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rectangle":
return shape.width * shape.height;
case "square":
return shape.size ** 2;
}
}
// 可辨识联合(Discriminated Unions)
// 通过共同的字段(kind)来区分不同类型
交叉类型(Intersection Types):
- 表示值必须同时满足多个类型
- 使用
&连接类型
// 基本交叉类型
interface Named {
name: string;
}
interface Aged {
age: number;
}
type Person = Named & Aged;
const person: Person = {
name: "Alice",
age: 25
};
// 交叉类型合并
type A = { a: string };
type B = { b: number };
type C = { c: boolean };
type ABC = A & B & C; // { a: string; b: number; c: boolean }
// 函数类型的交叉
type F1 = (x: number) => number;
type F2 = (y: string) => string;
type F3 = F1 & F2; // 既是 F1 也是 F2(实际上不可能,但类型系统允许)
字面量类型(Literal Types):
- 具体的值作为类型
- 字符串字面量、数字字面量、布尔字面量
// 字符串字面量类型
type Direction = "up" | "down" | "left" | "right";
function move(direction: Direction): void {
console.log(`Moving ${direction}`);
}
move("up"); // OK
move("north"); // Error: Argument of type '"north"' is not assignable...
// 数字字面量类型
type DiceValue = 1 | 2 | 3 | 4 | 5 | 6;
const rollDice = (): DiceValue => {
return Math.floor(Math.random() * 6 + 1) as DiceValue;
};
// 布尔字面量类型(较少使用)
type TrueOnly = true;
// 与联合类型结合
type StatusCode = 200 | 301 | 400 | 404 | 500;
type Method = "GET" | "POST" | "PUT" | "DELETE";
interface RequestConfig {
method: Method;
url: string;
timeout?: number;
}
枚举(Enum):
- 定义命名常量集合
- 有数字枚举和字符串枚举两种
// 1. 数字枚举(默认)
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
// 自定义起始值
enum Status {
Pending = 1, // 1
Approved, // 2
Rejected // 3
}
// 2. 字符串枚举
enum LogLevel {
Error = "ERROR",
Warn = "WARN",
Info = "INFO",
Debug = "DEBUG"
}
// 3. 常量枚举(编译时完全删除,性能更好)
const enum MediaType {
JSON = "application/json",
XML = "application/xml",
Form = "application/x-www-form-urlencoded"
}
// 4. 异构枚举(混合字符串和数字,不推荐)
enum BooleanLike {
No = 0,
Yes = "YES"
}
// 枚举的编译结果
enum Color {
Red,
Green,
Blue
}
// 编译为:
// var Color;
// (function (Color) {
// Color[Color["Red"] = 0] = "Red";
// Color[Color["Green"] = 1] = "Green";
// Color[Color["Blue"] = 2] = "Blue";
// })(Color || (Color = {}));
// 使用示例
function setDirection(direction: Direction): void {
console.log(`Setting direction to ${Direction[direction]}`);
}
setDirection(Direction.Left); // "Setting direction to Left"
// 枚举反向映射
console.log(Direction.Up); // 0
console.log(Direction[0]); // "Up"
console.log(Direction["Up"]); // 0
最佳实践:
// 1. 使用联合类型代替枚举的简单场景
type Direction = "up" | "down" | "left" | "right";
// 优点:更轻量,无需运行时对象,与普通字符串兼容
// 2. 需要使用反向映射时使用枚举
enum HttpMethod {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE"
}
// 3. 常量枚举用于性能优化
const enum ResponseCode {
Success = 200,
NotFound = 404,
ServerError = 500
}
补充说明:
- 联合类型是 TypeScript 类型系统的核心,支持强大的类型守卫
- 交叉类型常用于组合多个类型,实现类似“多重继承”
- 字面量类型可以与模板字面量类型结合,创建复杂的字符串模式
- 枚举会生成运行时代码,增加包体积,考虑使用联合类型替代
五、特殊类型
5. any vs unknown vs void vs never
问题:TypeScript any 类型与 unknown 类型的区别?TypeScript void 类型与 never 类型的区别?
答案:
any vs unknown 对比表:
| 特性 | any | unknown |
|---|---|---|
| 类型检查 | 禁用所有类型检查 | 保持类型安全 |
| 赋值 | 可以赋给任意类型 | 只能赋给 unknown 或 any |
| 操作 | 允许任意操作 | 不允许任何操作(需先类型断言) |
| 安全性 | 不安全,可能运行时错误 | 安全,强制类型检查 |
| 使用场景 | 迁移旧代码、第三方库 | 替代 any,提高类型安全 |
代码示例:
// any:禁用类型检查
let value: any = "hello";
value = 42; // OK
value.toUpperCase(); // 编译时OK,运行时OK(String方法)
value.foo.bar.baz(); // 编译时OK,运行时可能报错
// unknown:类型安全
let unsure: unknown = "hello";
unsure = 42; // OK
// unsure.toUpperCase(); // 编译时报错:Object is of type 'unknown'
// 使用 unknown 需要类型检查或类型断言
if (typeof unsure === "string") {
console.log(unsure.toUpperCase()); // OK,经过类型守卫
}
let str: string = unsure as string; // 类型断言
let num: number = <number>unsure; // 另一种断言语法
// unknown 比 any 更安全
function process(value: unknown): void {
if (typeof value === "string") {
console.log(value.length);
} else if (Array.isArray(value)) {
console.log(value.length);
}
// 必须检查类型后才能操作
}
void vs never 对比表:
| 特性 | void | never |
|---|---|---|
| 含义 | 没有返回值 | 永远不会返回 |
| 值 | 可以是 undefined 或 null(严格模式仅 undefined) | 没有值 |
| 使用场景 | 函数没有返回值 | 抛出异常、无限循环、类型检查中的不可能情况 |
| 返回值 | 允许返回 undefined | 不允许返回任何值 |
代码示例:
// void:函数没有返回值
function logMessage(message: string): void {
console.log(message);
// 隐式返回 undefined
}
function noReturn(): void {
// 没有 return 语句
}
// 注意:void 类型的变量只能赋值为 undefined 或 null(非严格模式)
let unusable: void = undefined;
// unusable = null; // 严格模式下报错
// never:永远不会返回的函数
function throwError(message: string): never {
throw new Error(message);
// 永远不会执行到函数末尾
}
function infiniteLoop(): never {
while (true) {
// 无限循环
}
// 永远不会返回
}
// never 在类型系统中的应用
type NonNullable<T> = T extends null | undefined ? never : T;
// 条件类型中排除 null 和 undefined
type T1 = NonNullable<string | null | undefined>; // string
// 穷尽性检查
type Shape = "circle" | "square" | "triangle";
function getArea(shape: Shape): number {
switch (shape) {
case "circle":
return Math.PI;
case "square":
return 1;
case "triangle":
return 0.5;
default:
// 类型检查确保所有情况都已处理
const exhaustiveCheck: never = shape;
throw new Error(`Unhandled shape: ${exhaustiveCheck}`);
}
}
最佳实践:
- 避免使用 any:尽可能使用具体的类型
- 使用 unknown 替代 any:当类型不确定时,使用 unknown 提高安全性
- 合理使用 void:表示函数没有返回值
- 善用 never:用于永远不会返回的函数和类型系统的高级特性
补充说明:
any会破坏 TypeScript 的类型安全性,应尽量避免unknown是 TypeScript 3.0 引入的类型安全的any替代品never类型在条件类型、映射类型中非常有用,是 TypeScript 类型系统的重要基础
六、泛型
6. 泛型基础与约束
问题:TypeScript 泛型的作用是什么?TypeScript 泛型约束的使用?
答案:
泛型的作用:
- 类型复用:创建可重用的组件,适用于多种类型
- 类型安全:保持类型信息,避免使用
any - 代码灵活:允许用户指定具体类型
- 约束行为:通过约束限制可用的类型
基本语法:
// 1. 泛型函数
function identity<T>(value: T): T {
return value;
}
// 使用类型参数显式指定
let output1 = identity<string>("hello"); // 输出类型为 string
// 使用类型推断
let output2 = identity(42); // 输出类型为 number
// 2. 泛型接口
interface Box<T> {
content: T;
}
let stringBox: Box<string> = { content: "hello" };
let numberBox: Box<number> = { content: 42 };
// 3. 泛型类
class Container<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
setValue(newValue: T): void {
this.value = newValue;
}
}
const stringContainer = new Container("hello");
const numberContainer = new Container(42);
// 4. 泛型约束
interface Lengthwise {
length: number;
}
// 约束 T 必须具有 length 属性
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity("hello"); // OK,字符串有 length
loggingIdentity([1, 2, 3]); // OK,数组有 length
// loggingIdentity(42); // Error,数字没有 length
// 5. 多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
const swapped = swap(["hello", 42]); // 类型为 [number, string]
// 6. 泛型约束中使用 keyof
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: "Alice", age: 25 };
getProperty(person, "name"); // OK,返回 string
getProperty(person, "age"); // OK,返回 number
// getProperty(person, "salary"); // Error,"salary" 不是 person 的键
// 7. 泛型默认类型
interface PaginatedResponse<T = any> {
data: T[];
total: number;
page: number;
pageSize: number;
}
// 使用默认类型
const response1: PaginatedResponse = {
data: [1, 2, 3],
total: 3,
page: 1,
pageSize: 10
};
// 指定具体类型
interface User {
id: number;
name: string;
}
const response2: PaginatedResponse<User> = {
data: [{ id: 1, name: "Alice" }],
total: 1,
page: 1,
pageSize: 10
};
// 8. 条件类型中的泛型
type IsString<T> = T extends string ? "yes" : "no";
type T1 = IsString<string>; // "yes"
type T2 = IsString<number>; // "no"
// 9. 映射类型中的泛型
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 应用
interface Todo {
title: string;
description: string;
completed: boolean;
}
type ReadonlyTodo = Readonly<Todo>;
type PartialTodo = Partial<Todo>;
泛型约束的高级用法:
// 1. 构造函数约束
interface Constructor<T> {
new(...args: any[]): T;
}
function createInstance<T>(ctor: Constructor<T>, ...args: any[]): T {
return new ctor(...args);
}
class Person {
constructor(public name: string) {}
}
const person = createInstance(Person, "Alice"); // Person 实例
// 2. 多个约束
interface Serializable {
serialize(): string;
}
interface Deserializable {
deserialize(data: string): void;
}
function process<T extends Serializable & Deserializable>(obj: T): void {
const data = obj.serialize();
// 处理数据
obj.deserialize(data);
}
// 3. 约束为特定类型
function merge<T extends object, U extends object>(first: T, second: U): T & U {
return { ...first, ...second };
}
const merged = merge({ name: "Alice" }, { age: 25 });
// 类型为 { name: string } & { age: number }
补充说明:
- 泛型是 TypeScript 最强大的特性之一,是构建可复用类型安全代码的基础
- 合理使用泛型约束,避免过于宽松的类型
- 泛型与条件类型、映射类型结合可以实现复杂的类型操作
七、工具类型
7. 常用工具类型详解
问题:TypeScript 工具类型有哪些?Partial、Required、Readonly 类型的使用?Pick、Omit 类型的使用?Record 类型的使用?Exclude、Extract 类型的使用?ReturnType、Parameters 类型的使用?NonNullable 类型的使用?
答案:
内置工具类型概览:
| 工具类型 | 作用 | 示例 |
|---|---|---|
Partial<T> | 将所有属性变为可选 | Partial<User> → { name?: string; age?: number } |
Required<T> | 将所有属性变为必选 | Required<PartialUser> → { name: string; age: number } |
Readonly<T> | 将所有属性变为只读 | Readonly<User> → { readonly name: string; readonly age: number } |
Pick<T, K> | 从 T 中选取部分属性 K | Pick<User, "name"> → { name: string } |
Omit<T, K> | 从 T 中排除部分属性 K | Omit<User, "age"> → { name: string } |
Record<K, T> | 创建键类型为 K,值类型为 T 的对象 | Record<string, number> → { [key: string]: number } |
Exclude<T, U> | 从 T 中排除可赋值给 U 的类型 | Exclude<"a"|"b"|"c", "a"> → "b" | "c" |
Extract<T, U> | 提取 T 中可赋值给 U 的类型 | Extract<"a"|"b"|"c", "a"|"d"> → "a" |
NonNullable<T> | 从 T 中排除 null 和 undefined | NonNullable<string | null> → string |
ReturnType<T> | 获取函数返回值类型 | ReturnType<typeof setTimeout> → number |
Parameters<T> | 获取函数参数类型元组 | Parameters<(x: number, y: string) => void> → [number, string] |
ConstructorParameters<T> | 获取构造函数参数类型元组 | ConstructorParameters<typeof Error> → [string?] |
InstanceType<T> | 获取构造函数实例类型 | InstanceType<typeof Array> → any[] |
ThisParameterType<T> | 获取函数 this 参数类型 | |
OmitThisParameter<T> | 移除函数 this 参数类型 |
详细示例:
// 基础类型
interface User {
id: number;
name: string;
age: number;
email?: string;
}
// 1. Partial - 所有属性变为可选
type PartialUser = Partial<User>;
// 等价于:{ id?: number; name?: string; age?: number; email?: string }
// 2. Required - 所有属性变为必选
type RequiredUser = Required<User>;
// 等价于:{ id: number; name: string; age: number; email: string }
// 3. Readonly - 所有属性变为只读
type ReadonlyUser = Readonly<User>;
// 等价于:{ readonly id: number; readonly name: string; readonly age: number; readonly email?: string }
// 4. Pick - 选择部分属性
type UserBasicInfo = Pick<User, "id" | "name">;
// 等价于:{ id: number; name: string }
// 5. Omit - 排除部分属性
type UserWithoutId = Omit<User, "id">;
// 等价于:{ name: string; age: number; email?: string }
// 6. Record - 创建记录类型
type PageInfo = Record<"home" | "about" | "contact", string>;
// 等价于:{ home: string; about: string; contact: string }
type Dictionary<T> = Record<string, T>;
// 等价于:{ [key: string]: T }
// 7. Exclude - 排除类型
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<string | number | (() => void), Function>; // string | number
type T2 = Exclude<User | null | undefined, null | undefined>; // User
// 8. Extract - 提取类型
type T3 = Extract<"a" | "b" | "c", "a" | "c">; // "a" | "c"
type T4 = Extract<string | number | (() => void), Function>; // () => void
// 9. NonNullable - 排除 null 和 undefined
type T5 = NonNullable<string | number | null | undefined>; // string | number
// 10. ReturnType - 函数返回值类型
type T6 = ReturnType<() => string>; // string
type T7 = ReturnType<<T>() => T>; // unknown
type T8 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]
// 实际应用
declare function f1(): { a: number; b: string };
type T9 = ReturnType<typeof f1>; // { a: number; b: string }
// 11. Parameters - 函数参数类型
type T10 = Parameters<() => string>; // []
type T11 = Parameters<(s: string) => void>; // [string]
type T12 = Parameters<<T>(arg: T) => T>; // [unknown]
// 实际应用
declare function f2(arg: { a: number; b: string }): void;
type T13 = Parameters<typeof f2>; // [{ a: number; b: string }]
// 12. 组合使用工具类型
type UserPreview = Pick<User, "id" | "name"> & Partial<Pick<User, "email">>;
// 等价于:{ id: number; name: string; email?: string }
type ReadonlyPartial<T> = Readonly<Partial<T>>;
type Nullable<T> = T | null | undefined;
type MaybeArray<T> = T | T[];
// 13. 自定义工具类型
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
type ValueOf<T> = T[keyof T];
// 应用
interface Company {
name: string;
address: {
city: string;
street: string;
};
}
type DeepPartialCompany = DeepPartial<Company>;
// 等价于:{
// name?: string;
// address?: {
// city?: string;
// street?: string;
// };
// }
// 14. 条件类型与工具类型结合
type Diff<T, U> = T extends U ? never : T;
type Filter<T, U> = T extends U ? T : never;
type T14 = Diff<"a" | "b" | "c", "a" | "b">; // "c"
type T15 = Filter<"a" | "b" | "c", "a" | "b">; // "a" | "b"
// 15. 模板字面量类型与工具类型(TypeScript 4.1+)
type EventName = "click" | "scroll" | "mousemove";
type HandlerName = `on${Capitalize<EventName>}`;
// 等价于:"onClick" | "onScroll" | "onMousemove"
// 16. 类型谓词工具类型
type Predicate<T> = (value: unknown) => value is T;
function isString(value: unknown): value is string {
return typeof value === "string";
}
type StringPredicate = Predicate<string>;
手写实现(理解原理):
// Partial 实现原理
type MyPartial<T> = {
[P in keyof T]?: T[P];
};
// Required 实现原理
type MyRequired<T> = {
[P in keyof T]-?: T[P];
};
// Readonly 实现原理
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};
// Pick 实现原理
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
// Omit 实现原理(通过 Pick 和 Exclude)
type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// Record 实现原理
type MyRecord<K extends keyof any, T> = {
[P in K]: T;
};
// Exclude 实现原理
type MyExclude<T, U> = T extends U ? never : T;
// Extract 实现原理
type MyExtract<T, U> = T extends U ? T : never;
// NonNullable 实现原理
type MyNonNullable<T> = T extends null | undefined ? never : T;
// ReturnType 实现原理
type MyReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
// Parameters 实现原理
type MyParameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
补充说明:
- 工具类型是 TypeScript 类型编程的核心,理解它们有助于编写更灵活的类型定义
- 条件类型(
extends ? :)和映射类型(in keyof)是构建工具类型的基础 infer关键字用于在条件类型中提取类型信息- 工具类型可以组合使用,创建更复杂的类型操作
八、类型推断与断言
8. 类型推断、类型断言与类型守卫
问题:TypeScript 类型推断的规则?TypeScript 类型断言的使用?TypeScript 类型断言与类型转换的区别?TypeScript 类型守卫的使用?TypeScript 类型守卫的类型有哪些?TypeScript 类型谓词的使用?
答案:
类型推断规则:
- 基本类型推断:从赋值推断类型
- 最佳通用类型:从多个值推断联合类型
- 上下文类型:从上下文推断类型(如函数参数)
- 类型拓宽:字面量类型会拓宽为基本类型
// 1. 基本类型推断
let x = 3; // 推断为 number
let y = "hello"; // 推断为 string
let z = [1, 2, 3]; // 推断为 number[]
// 2. 最佳通用类型
let arr = [0, 1, null]; // 推断为 (number | null)[]
let mixed = ["hello", 42, true]; // 推断为 (string | number | boolean)[]
// 3. 上下文类型
window.onmousedown = function(event) {
// event 被推断为 MouseEvent
console.log(event.clientX, event.clientY);
};
// 4. 类型拓宽
const constStr = "hello"; // 类型为 "hello"(字面量类型)
let letStr = "hello"; // 类型为 string(拓宽)
// 使用 const 断言防止拓宽
let notWidened = "hello" as const; // 类型为 "hello"
let tuple = [1, 2] as const; // 类型为 readonly [1, 2]
类型断言:
- 告诉 TypeScript 你比它更了解类型信息
- 两种语法:
as和<Type>
// 1. as 语法(推荐)
let someValue: any = "this is a string";
let strLength1: number = (someValue as string).length;
// 2. 尖括号语法(在 JSX 中会冲突)
let strLength2: number = (<string>someValue).length;
// 3. 非空断言操作符 !
let maybeString: string | null = getString();
let length: number = maybeString!.length; // 断言 maybeString 不为 null/undefined
// 4. 双重断言(类型断言链)
let value: unknown = "hello";
// let str: string = value; // Error: unknown 不能直接赋值给 string
let str1: string = value as string; // 需要类型断言
let str2: string = value as any as string; // 双重断言,绕过类型检查
// 5. const 断言
let point = {
x: 10,
y: 20
} as const; // 类型为 { readonly x: 10; readonly y: 20; }
// 实际应用场景
// 场景1:处理 DOM 元素
const canvas = document.getElementById("canvas") as HTMLCanvasElement;
const ctx = canvas.getContext("2d")!;
// 场景2:处理 API 响应
interface ApiResponse {
data: User[];
total: number;
}
fetch("/api/users")
.then(res => res.json())
.then(data => {
const response = data as ApiResponse;
console.log(response.total);
});
// 场景3:处理第三方库
declare function getData(): unknown;
const result = getData() as { id: number; name: string };
类型断言 vs 类型转换:
- 类型断言:编译时概念,告诉编译器类型信息,不改变运行时的值
- 类型转换:运行时操作,实际转换值的类型
// 类型断言(编译时)
let str: any = "123";
let num1: number = str as number; // 编译时通过,运行时 str 仍然是字符串
// 类型转换(运行时)
let num2: number = Number(str); // 运行时将字符串转换为数字
let num3: number = parseInt(str); // 运行时解析字符串为数字
// 正确使用类型转换
function processInput(input: string | number): number {
if (typeof input === "string") {
return parseFloat(input); // 类型转换
}
return input; // 直接返回数字
}
类型守卫:
- 运行时检查,用于缩小类型范围
- 主要方式:
typeof、instanceof、in、自定义类型谓词
// 1. typeof 类型守卫
function padLeft(value: string, padding: string | number): string {
if (typeof padding === "number") {
return " ".repeat(padding) + value;
}
return padding + value;
}
// 2. instanceof 类型守卫
class Bird {
fly() {
console.log("flying");
}
}
class Fish {
swim() {
console.log("swimming");
}
}
function move(pet: Bird | Fish): void {
if (pet instanceof Bird) {
pet.fly();
} else {
pet.swim();
}
}
// 3. in 操作符类型守卫
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
function makeSound(pet: Cat | Dog): void {
if ("meow" in pet) {
pet.meow();
} else {
pet.bark();
}
}
// 4. 自定义类型守卫(类型谓词)
function isString(value: unknown): value is string {
return typeof value === "string";
}
function isNumber(value: unknown): value is number {
return typeof value === "number" && !isNaN(value);
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
function isRectangle(shape: Rectangle | Circle): shape is Rectangle {
return shape.kind === "rectangle";
}
function area(shape: Rectangle | Circle): number {
if (isRectangle(shape)) {
return shape.width * shape.height;
} else {
return Math.PI * shape.radius ** 2;
}
}
// 5. 可辨识联合(Discriminated Unions)
type Shape = Rectangle | Circle;
function getArea(shape: Shape): number {
switch (shape.kind) {
case "rectangle":
return shape.width * shape.height;
case "circle":
return Math.PI * shape.radius ** 2;
}
}
// 6. 类型断言守卫(Type Assertion Guards)
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new Error("Not a string!");
}
}
function processValue(value: unknown): void {
assertIsString(value);
// 这里 value 被断言为 string
console.log(value.toUpperCase());
}
类型谓词:
- 自定义类型守卫的函数返回值类型
- 格式:
parameterName is Type
// 复杂类型谓词
interface Admin {
role: "admin";
permissions: string[];
}
interface User {
role: "user";
name: string;
}
type Person = Admin | User;
function isAdmin(person: Person): person is Admin {
return person.role === "admin";
}
function getPermissions(person: Person): string[] | null {
if (isAdmin(person)) {
return person.permissions; // person 类型为 Admin
}
return null; // person 类型为 User
}
// 数组类型谓词
function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every(item => typeof item === "string");
}
function processArray(arr: unknown): void {
if (isStringArray(arr)) {
// arr 类型为 string[]
arr.forEach(str => console.log(str.toUpperCase()));
}
}
// 使用类型谓词处理 API 响应
interface SuccessResponse<T> {
success: true;
data: T;
}
interface ErrorResponse {
success: false;
error: string;
}
type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;
function isSuccessResponse<T>(
response: ApiResponse<T>
): response is SuccessResponse<T> {
return response.success;
}
async function fetchData<T>(url: string): Promise<T | null> {
const response: ApiResponse<T> = await fetch(url).then(res => res.json());
if (isSuccessResponse(response)) {
return response.data;
} else {
console.error(response.error);
return null;
}
}
补充说明:
- 优先使用类型守卫而不是类型断言,类型守卫更安全可靠
- 类型断言应在了解代码逻辑的情况下谨慎使用
- 自定义类型谓词可以提高代码的类型安全性和可读性
- 可辨识联合是处理复杂类型分支的优雅方式
九、装饰器
9. 装饰器类型与使用
问题:TypeScript 装饰器的作用?TypeScript 装饰器的类型有哪些?TypeScript 类装饰器、属性装饰器、方法装饰器、参数装饰器的使用?TypeScript 装饰器元数据?
答案:
装饰器概述:
- 装饰器是特殊的声明,可以附加到类、方法、属性、参数上
- 修改或扩展它们的行为
- 实验性特性,需要在
tsconfig.json中启用"experimentalDecorators": true - 本质上是一个函数,在运行时被调用
装饰器类型:
- 类装饰器:应用于类构造函数
- 方法装饰器:应用于方法
- 属性装饰器:应用于属性
- 参数装饰器:应用于参数
- 访问器装饰器:应用于 getter/setter
基本语法:
// 装饰器工厂(带参数)
function DecoratorFactory(config: any) {
return function(target: any) {
// 装饰器逻辑
};
}
// 装饰器应用
@DecoratorFactory({ option: true })
class MyClass {
@PropertyDecorator()
property: string;
@MethodDecorator()
method(@ParameterDecorator() param: string) {}
@AccessorDecorator()
get accessor() { return this.property; }
}
类装饰器:
// 类装饰器签名
type ClassDecorator = <TFunction extends Function>(
target: TFunction
) => TFunction | void;
// 示例1:简单类装饰器
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
// 示例2:类装饰器工厂
function classDecoratorFactory(prefix: string) {
return function<T extends { new(...args: any[]): {} }>(constructor: T) {
return class extends constructor {
className = prefix + constructor.name;
};
};
}
@classDecoratorFactory("MyPrefix-")
class MyClass {
name = "Test";
}
const instance = new MyClass();
console.log((instance as any).className); // "MyPrefix-MyClass"
// 示例3:替换类实现
function replaceClass<T extends { new(...args: any[]): {} }>(constructor: T) {
return class extends constructor {
newProperty = "new property";
hello = "override";
};
}
@replaceClass
class Greeter2 {
property = "property";
hello: string;
constructor(m: string) {
this.hello = m;
}
}
const g = new Greeter2("world");
console.log(g.hello); // "override"
console.log((g as any).newProperty); // "new property"
方法装饰器:
// 方法装饰器签名
type MethodDecorator = <T>(
target: Object,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;
// 示例1:日志装饰器
function log(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`调用方法: ${propertyKey}`);
console.log(`参数: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`返回值: ${result}`);
return result;
};
return descriptor;
}
class Calculator {
@log
add(x: number, y: number): number {
return x + y;
}
}
const calc = new Calculator();
calc.add(2, 3); // 输出日志信息
// 示例2:性能测量装饰器
function measureTime(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
const start = performance.now();
const result = originalMethod.apply(this, args);
const end = performance.now();
console.log(`${propertyKey} 执行时间: ${end - start}ms`);
return result;
};
return descriptor;
}
class DataProcessor {
@measureTime
processData(data: number[]): number[] {
// 模拟数据处理
return data.map(x => x * 2).filter(x => x > 10);
}
}
// 示例3:缓存装饰器
function cache(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const cacheMap = new Map();
descriptor.value = function(...args: any[]) {
const key = JSON.stringify(args);
if (cacheMap.has(key)) {
console.log(`缓存命中: ${propertyKey}`);
return cacheMap.get(key);
}
const result = originalMethod.apply(this, args);
cacheMap.set(key, result);
return result;
};
return descriptor;
}
class MathUtils {
@cache
expensiveCalculation(x: number, y: number): number {
// 模拟耗时计算
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.sin(x) * Math.cos(y);
}
return result;
}
}
属性装饰器:
// 属性装饰器签名
type PropertyDecorator = (
target: Object,
propertyKey: string | symbol
) => void;
// 示例1:只读属性装饰器
function readonly(target: Object, propertyKey: string) {
const descriptor: PropertyDescriptor = {
writable: false,
configurable: false
};
Object.defineProperty(target, propertyKey, descriptor);
}
class User {
@readonly
id: number;
name: string;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
}
const user = new User(1, "Alice");
// user.id = 2; // Error: Cannot assign to read only property 'id'
// 示例2:格式验证装饰器
function validateEmail(target: Object, propertyKey: string) {
let value: string;
const getter = function() {
return value;
};
const setter = function(newVal: string) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(newVal)) {
throw new Error(`Invalid email: ${newVal}`);
}
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
class Contact {
@validateEmail
email: string;
constructor(email: string) {
this.email = email;
}
}
// const contact = new Contact("invalid-email"); // Error: Invalid email
// 示例3:监听属性变化
function watch(target: Object, propertyKey: string) {
const privateKey = `_${propertyKey}`;
Object.defineProperty(target, privateKey, {
writable: true,
enumerable: false,
configurable: true
});
const getter = function() {
return (this as any)[privateKey];
};
const setter = function(newVal: any) {
const oldVal = (this as any)[privateKey];
(this as any)[privateKey] = newVal;
if (oldVal !== newVal) {
console.log(`属性 ${propertyKey} 从 ${oldVal} 变为 ${newVal}`);
// 可以触发自定义事件
if ((this as any).onPropertyChange) {
(this as any).onPropertyChange(propertyKey, oldVal, newVal);
}
}
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
class Observable {
@watch
value: number = 0;
onPropertyChange?: (key: string, oldVal: any, newVal: any) => void;
}
参数装饰器:
// 参数装饰器签名
type ParameterDecorator = (
target: Object,
propertyKey: string | symbol | undefined,
parameterIndex: number
) => void;
// 示例1:参数验证装饰器
function validateParam(
target: Object,
propertyKey: string | symbol,
parameterIndex: number
) {
const existingValidations: number[] =
Reflect.getMetadata("validations", target, propertyKey) || [];
existingValidations.push(parameterIndex);
Reflect.defineMetadata("validations", existingValidations, target, propertyKey);
}
// 示例2:依赖注入装饰器
function inject(token: string) {
return function(
target: Object,
propertyKey: string | symbol | undefined,
parameterIndex: number
) {
const existingInjections: { index: number; token: string }[] =
Reflect.getMetadata("injections", target) || [];
existingInjections.push({ index: parameterIndex, token });
Reflect.defineMetadata("injections", existingInjections, target);
};
}
// 结合方法装饰器使用
function validateMethod(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const validations = Reflect.getMetadata("validations", target, propertyKey) || [];
descriptor.value = function(...args: any[]) {
for (const index of validations) {
if (args[index] === undefined || args[index] === null) {
throw new Error(`参数 ${index} 不能为空`);
}
}
return originalMethod.apply(this, args);
};
return descriptor;
}
class Service {
@validateMethod
process(
@validateParam id: number,
@validateParam name: string,
optional?: string
) {
console.log(`处理: ${id} - ${name}`);
}
}
装饰器元数据:
- 需要
reflect-metadata库支持 - 在
tsconfig.json中启用"emitDecoratorMetadata": true - 可以存储和检索装饰器的元数据
import "reflect-metadata";
// 定义元数据键
const METADATA_KEY = {
designType: "design:type",
designParamTypes: "design:paramtypes",
designReturnType: "design:returntype"
};
// 使用元数据
function logTypes(target: Object, propertyKey: string) {
// 获取属性类型
const propType = Reflect.getMetadata(METADATA_KEY.designType, target, propertyKey);
console.log(`${propertyKey} 类型:`, propType);
// 获取方法参数类型
const paramTypes = Reflect.getMetadata(METADATA_KEY.designParamTypes, target, propertyKey);
if (paramTypes) {
console.log(`${propertyKey} 参数类型:`, paramTypes);
}
// 获取方法返回值类型
const returnType = Reflect.getMetadata(METADATA_KEY.designReturnType, target, propertyKey);
if (returnType) {
console.log(`${propertyKey} 返回值类型:`, returnType);
}
}
class Example {
@logTypes
property: string = "";
@logTypes
method(param1: number, param2: string): boolean {
return true;
}
}
// 输出:
// property 类型: [Function: String]
// method 参数类型: [ [Function: Number], [Function: String] ]
// method 返回值类型: [Function: Boolean]
装饰器执行顺序:
- 参数装饰器(方法或构造函数)
- 方法/属性/访问器装饰器
- 类装饰器
实际应用场景:
- 日志记录:记录方法的调用和参数
- 性能监控:测量方法执行时间
- 缓存:缓存方法结果
- 验证:验证参数和属性
- 依赖注入:自动注入依赖
- 权限控制:检查用户权限
- 事务管理:管理数据库事务
补充说明:
- 装饰器是实验性特性,未来可能变化
- 在 Angular、NestJS 等框架中广泛使用
- 合理使用装饰器可以提高代码的可维护性和可扩展性
- 注意装饰器的执行顺序和性能影响
十、配置与最佳实践
10. TypeScript 项目配置
问题:TypeScript tsconfig.json 的配置项?TypeScript 编译选项?TypeScript 严格模式的配置?TypeScript 路径映射的配置?TypeScript 声明文件的配置?
答案:
tsconfig.json 结构:
{
"compilerOptions": {
/* 基本选项 */
"target": "es5",
"module": "commonjs",
"lib": ["es6", "dom"],
/* 严格模式选项 */
"strict": true,
/* 模块解析选项 */
"moduleResolution": "node",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
/* 输出选项 */
"outDir": "./dist",
"sourceMap": true,
"declaration": true,
/* 其他选项 */
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
重要编译选项详解:
{
"compilerOptions": {
// 1. 目标版本
"target": "es2015", // 编译目标:es3, es5, es6/es2015, es2016, es2017, es2018, es2019, es2020, esnext
// 2. 模块系统
"module": "commonjs", // 模块系统:commonjs, amd, system, umd, es6/es2015, es2020, esnext
// 3. 库文件
"lib": ["dom", "es6", "dom.iterable", "es2017"], // 包含的库文件
// 4. 输出目录
"outDir": "./dist", // 输出目录
"rootDir": "./src", // 输入目录
// 5. 源映射
"sourceMap": true, // 生成 sourceMap
"inlineSourceMap": false, // 内联 sourceMap
// 6. 声明文件
"declaration": true, // 生成 .d.ts 声明文件
"declarationDir": "./types", // 声明文件输出目录
"declarationMap": true, // 为声明文件生成 sourceMap
// 7. 严格模式(建议开启)
"strict": true, // 启用所有严格类型检查选项
// 严格模式子选项
"noImplicitAny": true, // 禁止隐式 any 类型
"strictNullChecks": true, // 严格的 null 检查
"strictFunctionTypes": true, // 严格的函数类型检查
"strictBindCallApply": true, // 严格的 bind/call/apply 检查
"strictPropertyInitialization": true, // 严格的属性初始化检查
"noImplicitThis": true, // 禁止隐式的 this
"alwaysStrict": true, // 始终以严格模式解析
// 8. 额外检查
"noUnusedLocals": true, // 检查未使用的局部变量
"noUnusedParameters": true, // 检查未使用的参数
"noImplicitReturns": true, // 检查函数是否有隐式返回
"noFallthroughCasesInSwitch": true, // 检查 switch 语句的 fallthrough
// 9. 模块解析
"moduleResolution": "node", // 模块解析策略:node, classic
"baseUrl": ".", // 解析非相对模块的基础目录
"paths": { // 路径映射
"@/*": ["src/*"],
"components/*": ["src/components/*"]
},
"rootDirs": ["src", "generated"], // 根目录列表
// 10. 实验性功能
"experimentalDecorators": true, // 启用装饰器
"emitDecoratorMetadata": true, // 为装饰器生成元数据
// 11. 高级选项
"allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块进行默认导入
"esModuleInterop": true, // 启用 ES 模块互操作性
"skipLibCheck": true, // 跳过库文件的类型检查
"forceConsistentCasingInFileNames": true, // 强制文件名大小写一致
"resolveJsonModule": true, // 允许导入 JSON 文件
// 12. 输出选项
"removeComments": false, // 移除注释
"newLine": "lf", // 换行符:crlf, lf
// 13. 工程引用
"composite": true, // 启用工程引用
"incremental": true // 启用增量编译
},
// 文件包含/排除
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"tests/**/*.ts"
],
"exclude": [
"node_modules",
"dist",
"**/*.spec.ts",
"**/*.test.ts"
],
// 工程引用
"references": [
{ "path": "./shared" },
{ "path": "./frontend" },
{ "path": "./backend" }
]
}
路径映射配置:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
// 基本路径映射
"@/*": ["src/*"],
// 别名映射
"components/*": ["src/components/*"],
"utils/*": ["src/utils/*"],
"types/*": ["src/types/*"],
// 精确映射
"lodash": ["node_modules/@types/lodash"],
// 通配符映射
"*": ["node_modules/@types/*", "types/*"]
}
}
}
声明文件配置:
{
"compilerOptions": {
// 生成声明文件
"declaration": true,
"declarationDir": "./dist/types",
"declarationMap": true,
// 类型根目录
"typeRoots": ["./node_modules/@types", "./src/types"],
// 包含的类型
"types": ["node", "jest", "react"]
}
}
不同环境配置:
// tsconfig.base.json - 基础配置
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
// tsconfig.app.json - 应用配置
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"lib": ["dom", "es6", "dom.iterable", "es2017"],
"jsx": "react"
},
"include": ["src/**/*"],
"exclude": ["**/*.test.ts", "**/*.spec.ts"]
}
// tsconfig.test.json - 测试配置
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist-test",
"rootDir": ".",
"types": ["jest", "node"]
},
"include": ["src/**/*", "tests/**/*"]
}
最佳实践配置示例:
{
"compilerOptions": {
/* 基础配置 */
"target": "es2018",
"module": "commonjs",
"lib": ["es2018"],
/* 严格模式(推荐) */
"strict": true,
/* 模块解析 */
"moduleResolution": "node",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
/* 输出配置 */
"outDir": "dist",
"rootDir": "src",
/* 源码映射 */
"sourceMap": true,
/* 声明文件 */
"declaration": true,
"declarationDir": "dist/types",
/* 互操作性 */
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
/* 额外检查 */
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
/* 实验性功能 */
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
/* 性能优化 */
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"incremental": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}
补充说明:
- 始终开启严格模式(
"strict": true),提高代码质量 - 使用路径映射(
paths)简化导入路径 - 为库项目生成声明文件(
declaration) - 根据项目需求选择合适的
target和module配置 - 使用工程引用(Project References)管理大型项目
总结
TypeScript涵盖了 79 道题目中的核心考点,通过系统化的讲解、详细代码示例和实用对比表格,帮助深入理解 TypeScript 的各个方面。从基础类型到高级特性,从装饰器到配置管理,全面覆盖了 TypeScript 开发所需的关键知识。
关键要点:
- TypeScript 是 JavaScript 的超集,提供了静态类型检查等优势
- 类型系统是 TypeScript 的核心,包含基本类型、接口、泛型等
- 工具类型和高级类型操作是 TypeScript 的强项
- 装饰器提供了元编程能力,适合框架和库开发
- 合理配置
tsconfig.json对项目开发至关重要
建议结合实际项目实践,深入掌握 TypeScript 的各种特性和最佳实践。