1. 什么是TypeScript?
一、核心定义
TypeScript 是 JavaScript 的超集(Superset),由 Microsoft 开发,通过添加 静态类型系统 和 编译时类型检查 增强 JavaScript 的可靠性和可维护性。
-
超集特性:所有 JavaScript 语法均可直接在 TypeScript 中使用(
.ts文件兼容.js内容)。 - 核心目标:解决 JavaScript 动态类型在大型项目中的维护难题。
二、核心特性
| 特性 | 描述 | 示例场景 |
|---|---|---|
| 静态类型检查 | 编译阶段检测类型错误,而非运行时 | 防止 undefined 调用、数值与字符串意外拼接 |
| 类型注解与推断 | 支持显式声明类型(let age: number),也能自动推断类型(let name = "Alice") |
函数参数类型约束、复杂对象结构校验 |
| 面向对象增强 | 提供类、接口、泛型、装饰器等完整 OOP 特性 | 企业级框架(Angular/NestJS)开发 |
| ES6+ 语法支持 | 原生支持模块化、解构赋值、异步操作等现代语法 | 使用 async/await 处理 API 请求 |
| 工具链友好 | 提供代码补全、接口提示、重构支持等 IDE 增强功能 | VS Code 开发体验优化 |
三、与 JavaScript 的核心差异
| 对比维度 | TypeScript | JavaScript |
|---|---|---|
| 文件扩展名 |
.ts/.tsx
|
.js/.jsx
|
| 执行方式 | 编译为 JS 后运行(tsc 或 babel) |
直接解析执行 |
| 类型检查 | 编译时静态检查 | 运行时动态判断 |
| 错误反馈 | 编码阶段提示类型问题 | 运行到具体代码才报错 |
| 适用规模 | 中大型项目、长期维护系统 | 小型脚本、快速原型开发 |
四、项目应用场景
1. 企业级前端框架
// React + TypeScript 组件示例
interface UserCardProps {
name: string;
age: number;
onSelect: (id: string) => void;
}
const UserCard: React.FC<UserCardProps> = ({ name, age, onSelect }) => (
<div onClick={() => onSelect("123")}>
<h2>{name}</h2>
<p>Age: {age}</p>
</div>
);
优势:
- 组件属性类型校验
- 事件回调函数签名约束
2. Node.js 服务端开发
// Express 路由处理示例
interface LoginRequest {
username: string;
password: string;
}
app.post("/login", (req: Request<LoginRequest>, res: Response) => {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ error: "Missing credentials" });
}
// ...
});
优势:
- 请求体数据结构校验
- 避免属性拼写错误
3. 工具库开发
// 通用数据处理工具函数
function deepClone<T extends object>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}
const original = { a: 1, b: { c: 2 } };
const cloned = deepClone(original); // 类型自动推断为 { a: number; b: { c: number } }
优势:
- 输入输出类型自动关联
- 泛型保证返回值类型一致性
五、核心优势总结
- 错误预防:编译阶段拦截 15% 以上潜在运行时错误(根据 Microsoft 统计数据)
- 代码可读性:类型注解作为“活文档”提升团队协作效率
- 重构安全:类型系统辅助安全的重命名、参数修改等操作
-
生态兼容:通过
.d.ts声明文件支持所有 JavaScript 库(如 lodash、jQuery) -
渐进式迁移:支持从 JavaScript 逐步升级,旧代码可保留
.js文件
六、典型代码示例
类型错误拦截
// 编译时报错(但 JavaScript 不会)
let price: number = 99;
price = "100"; // Error: Type 'string' is not assignable to type 'number'
智能类型推断
const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 }
];
// 自动推断 users 类型为 Array<{ name: string; age: number }>
const names = users.map(u => u.nme); // Error: Property 'nme' does not exist on type...
2. 类型声明和类型推断的区别,并举例应用
一、核心区别
| 对比维度 | 类型声明 | 类型推断 |
|---|---|---|
| 定义 | 开发者显式标注变量/函数的类型 | TypeScript 编译器自动推导类型 |
| 语法 | 通过 : type 语法显式标注 |
无特殊语法,根据赋值自动判断 |
| 控制权 | 完全由开发者控制 | 由编译器分析上下文推导 |
| 必要性 | 必要场景下强制使用(如复杂类型) | 可减少冗余代码,提升开发效率 |
| 可读性 | 明确传达类型意图 | 依赖代码上下文理解 |
二、类型声明详解
1. 核心场景
- 函数参数与返回值:明确接口契约
- 复杂对象结构:定义嵌套类型
- 第三方数据源:处理API响应等不确定类型的数据
2. 代码示例
// 显式声明函数类型
function add(a: number, b: number): number {
return a + b;
}
// 接口类型声明
interface User {
id: string;
name: string;
age?: number; // 可选属性
}
// 明确空值类型
let nullableValue: string | null = null;
3. 实际应用
场景:处理API响应数据
// 显式声明API响应结构
interface ApiResponse<T> {
code: number;
data: T;
message?: string;
}
// 明确函数返回值类型
async function fetchUser(id: string): Promise<ApiResponse<User>> {
const response = await axios.get(`/users/${id}`);
return response.data;
}
三、类型推断详解
1. 核心场景
- 变量初始化:简单类型赋值
- 数组/对象字面量:自动推断成员类型
-
函数返回值:根据
return语句推导
2. 代码示例
// 基础类型推断
let price = 99.5; // 推断为 number
const message = "Hello"; // 推断为 "Hello"(字面量类型)
// 对象推断
const user = { // 推断为 { id: string; name: string }
id: "U123",
name: "Alice"
};
// 函数返回值推断
function sum(a: number, b: number) { // 返回类型推断为 number
return a + b;
}
3. 实际应用
场景:React 组件 Props 解构
// 利用类型推断自动推导props
const Button = ({ children, type = "default" }) => {
// children 推断为 ReactNode,type 推断为 string
return <button className={type}>{children}</button>;
};
// 等价于显式声明:
const Button: FC<{
children: ReactNode;
type?: string
}> = ({ children, type = "default" }) => { ... };
四、对比决策指南
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 函数参数与返回值 | 强制类型声明 | 明确契约,避免调用时类型错误 |
| 复杂对象(嵌套超过2层) | 优先类型声明 | 提升可读性,便于团队协作 |
| 变量初始化(简单类型) | 使用类型推断 | 减少冗余代码,保持简洁 |
| 可能为空的变量(如API响应) | 必须类型声明 | 明确联合类型(如 `T |
| 第三方库调用(无类型声明文件) | 类型断言 + 声明 | 结合 as 语法和接口声明保证类型安全 |
五、进阶技巧
1. 类型断言(Type Assertion)
// 明确告知编译器具体类型
const input = document.getElementById("username") as HTMLInputElement;
const data = await response.json() as UserProfile;
2. 最佳实践建议
- 函数参数:始终显式声明类型(即使可推断)
- 对象字面量:超过2层嵌套时使用接口声明
- 公共API(如组件Props、SDK接口):强制类型声明
- 工具函数:返回值类型可依赖推断,复杂逻辑建议显式声明
六、易错点分析
1. 过度依赖推断导致意外行为
let items = [1, 2, "3"]; // 推断为 (number | string)[]
items.push(true); // 错误:boolean 不属于推断的联合类型
2. 推断过度字面化
const routes = {
home: "/", // 推断为 string
about: "/about"
};
routes.home = "/home"; // 允许,因为类型是 string
// 若需固定字面量类型,需显式声明:
const routes: {
home: "/";
about: "/about"
} = { ... }; // 此时修改路径会报错
3. 接口(Interface)
一、接口(Interface)的定义与作用
1. 核心定义
接口是 TypeScript 中用于 定义对象结构契约 的类型工具,主要用于描述:
- 对象属性类型
- 方法签名
- 索引签名
- 函数类型
- 类的公共成员
2. 核心作用
- 类型检查:确保对象符合预期的结构
- 代码可读性:显式声明数据模型
- 契约约束:强制类实现特定成员
- 扩展性:通过继承组合复杂类型
二、接口的使用场景
1. 定义复杂对象结构
// 用户数据模型
interface User {
id: string;
name: string;
age?: number; // 可选属性
readonly email: string; // 只读属性
}
// 使用约束
function printUser(user: User) {
console.log(user.name);
}
2. 函数类型约束
// 定义函数接口
interface SearchFunc {
(source: string, keyword: string): boolean;
}
// 实现函数
const mySearch: SearchFunc = (src, kw) => src.includes(kw);
3. 类实现契约
// 定义接口
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
// 类实现接口
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
}
4. 可索引类型
// 数组式接口
interface StringArray {
[index: number]: string;
}
const arr: StringArray = ["Alice", "Bob"];
5. 框架配置定义(如 React Props)
interface ButtonProps {
type: 'primary' | 'default';
onClick: () => void;
children?: React.ReactNode;
}
const Button: React.FC<ButtonProps> = ({ type, onClick, children }) => (
<button className={type} onClick={onClick}>{children}</button>
);
三、接口 vs 类型别名(type)
1. 核心区别对比
| 对比维度 | 接口(interface) | 类型别名(type) |
|---|---|---|
| 语法 | interface Name { ... } |
type Name = ... |
| 扩展方式 | 使用 extends 继承 |
使用交叉类型(&) |
| 合并能力 | 支持同名接口自动合并 | 不可重复定义 |
| 适用类型 | 对象/函数/类结构 | 任意类型(包括联合、元组、字面量等) |
| 实现类 | 可通过 implements 约束类 |
无法直接约束类 |
2. 代码示例对比
(1) 扩展方式
// 接口继承
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// 类型别名扩展
type Animal = {
name: string;
};
type Dog = Animal & {
breed: string
};
(2) 合并声明
// 接口自动合并
interface Box {
width: number;
}
interface Box {
height: number;
}
const box: Box = { width: 100, height: 200 }; // 合法
// 类型别名重复定义报错
type Box = { width: number };
type Box = { height: number }; // Error: Duplicate identifier 'Box'
(3) 联合类型
// 类型别名更适合联合类型
type Status = 'success' | 'error' | 'pending';
type Result = { data: string } | { error: string };
// 接口无法直接表达联合类型
interface Result {
data?: string;
error?: string;
} // 语义不同(可选属性 vs 互斥属性)
四、选择使用场景指南
| 场景 | 推荐使用 | 原因 |
|---|---|---|
| 定义对象结构 | 接口 | 支持继承和合并,更适合面向对象设计 |
| 定义函数类型 | 接口 | 更清晰的语法(interface Func { (): void }) |
| 联合/交叉类型 | 类型别名 | 更简洁的语法(`type A = B |
| 元组类型 | 类型别名 | 接口无法直接描述元组(可用但不够直观) |
| 字面量类型约束 | 类型别名 | 直接使用 `type Status = 'on' |
| 需要被类实现 | 接口 | 通过 implements 强制执行 |
五、实际项目应用建议
1. 优先使用接口的场景
- 公共 API 定义:如组件 Props、SDK 接口
- 复杂对象结构:如 API 响应数据模型
- 类约束:定义类必须实现的成员
2. 优先使用类型别名的场景
- 联合类型:如 Redux Action 类型
-
工具类型:如
type PartialUser = Partial<User> - 复杂类型运算:如条件类型、映射类型
3. 混合使用示例
// 接口定义基础结构
interface BaseResponse {
code: number;
message?: string;
}
// 类型别名扩展联合类型
type ApiResponse<T> = BaseResponse & (
{ code: 200; data: T } |
{ code: 400; error: string }
);
4. 泛型(Generics)
一、泛型的核心概念
1. 定义
泛型(Generics)是 类型参数化 的编程工具,允许在定义函数、类或接口时使用 类型占位符(如 <T>),在使用时指定具体类型,实现代码复用和类型安全。
2. 核心优势
-
类型安全:避免
any的过度使用 - 代码复用:一套逻辑处理多种类型
- 智能提示:保留完整的类型信息
二、泛型的创建与使用
1. 泛型函数
基础语法
function identity<T>(arg: T): T {
return arg;
}
// 使用
identity<string>("Hello"); // 显式指定类型
identity(42); // 自动推断为 number
多类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([1, "two"]); // 返回 ["two", 1]
2. 泛型类
基础实现
class Container<T> {
private value: T;
constructor(initialValue: T) {
this.value = initialValue;
}
getValue(): T {
return this.value;
}
}
// 使用
const numContainer = new Container<number>(100);
const strContainer = new Container("Hello"); // 推断为 string
实际应用:API 响应处理
class ApiResponse<T> {
constructor(
public code: number,
public data: T,
public message?: string
) {}
}
// 使用
const userResponse = new ApiResponse<User>(200, userData);
const errorResponse = new ApiResponse<string>(400, "Invalid input");
三、泛型的实际用途
1. 集合类工具
// 类型安全的数组操作
function filterByKey<T>(items: T[], key: keyof T, value: T[keyof T]): T[] {
return items.filter(item => item[key] === value);
}
// 使用
const users = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
filterByKey(users, "name", "Alice"); // 返回匹配项
2. React 组件 Props
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function GenericList<T>({ items, renderItem }: ListProps<T>) {
return <div>{items.map(renderItem)}</div>;
}
// 使用
<GenericList
items={users}
renderItem={user => <div>{user.name}</div>}
/>
3. 状态管理(如 Redux)
// 通用 Action 类型
type Action<T, P> = {
type: T;
payload: P;
};
// 用户登录 Action
type LoginAction = Action<"LOGIN", { token: string }>;
4. API 请求封装
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
return response.json() as T;
}
// 使用
interface Product {
id: string;
price: number;
}
const products = await fetchData<Product[]>("/api/products");
四、高级泛型技巧
1. 泛型约束(Constraints)
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): void {
console.log(arg.length);
}
logLength("hello"); // 5
logLength({ length: 100 }); // 100
logLength(42); // 错误:number 没有 length 属性
2. 默认类型参数
function createArray<T = string>(length: number, value: T): T[] {
return Array(length).fill(value);
}
createArray(3, "A"); // string[]
createArray<number>(3, 100); // number[]
3. 条件类型(Conditional Types)
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<123>; // false
五、项目实践建议
1. 何时使用泛型
- 需要处理 多种数据类型 但逻辑相同的场景
- 封装 通用工具库(如 lodash 风格函数)
- 定义 数据容器(如集合类、响应对象)
2. 避免过度使用
- 简单数据处理无需泛型(如仅处理 number 的计算)
- 过度泛型会降低代码可读性
3. 命名规范
- 使用 单字母大写(如
T,U,K) - 语义化命名(如
TData,TKey)
六、常见问题示例
错误:未约束的泛型属性访问
function getProperty<T>(obj: T, key: keyof T) {
return obj[key]; // 安全访问
}
// 正确用法
getProperty({ name: "Alice" }, "name");
正确:泛型与接口结合
interface HasId {
id: string;
}
function findById<T extends HasId>(items: T[], id: string): T | undefined {
return items.find(item => item.id === id);
}
5. 枚举(Enum)
一、枚举(Enum)的定义与优势
1. 核心定义
枚举是 TypeScript 提供的 组织命名常量集合 的特殊语法,支持 数字 和 字符串 两种基础类型。
2. 核心优势
| 优势 | 说明 |
|---|---|
| 代码可读性 | 用有意义的名称替代魔法数字(如 OrderStatus.Paid vs 2) |
| 类型安全 | 限制变量取值范围,避免无效状态 |
| 集中管理 | 统一维护常量值,修改时无需全局搜索 |
| 双向映射 | 数字枚举支持正向(name → value)和反向(value → name)查找 |
二、枚举的应用案例
1. 状态码管理
enum HttpStatus {
OK = 200,
BadRequest = 400,
Unauthorized = 401,
InternalError = 500
}
function handleResponse(code: HttpStatus) {
if (code === HttpStatus.OK) {
// 处理成功响应
}
}
2. 业务状态流转
enum OrderStatus {
Draft = "DRAFT", // 草稿
Paid = "PAID", // 已支付
Delivered = "DELIVERED" // 已发货
}
function updateOrder(status: OrderStatus) {
// ...
}
updateOrder(OrderStatus.Paid);
3. 权限角色控制
enum UserRole {
Guest = 0,
User = 1,
Admin = 2
}
function checkPermission(role: UserRole) {
return role >= UserRole.Admin;
}
三、常量枚举(const enum)
1. 核心特性
编译优化:常量枚举在编译阶段被完全删除,值直接内联到使用位置
性能优势:无运行时对象生成,减少代码体积
-
限制 :
- 只能使用常量表达式初始化
- 不支持计算成员
- 无法通过
Object.keys()等反射方法访问
2. 代码示例
const enum Direction {
Up = "UP",
Down = "DOWN"
}
console.log(Direction.Up); // 编译后替换为 "UP"
四、普通枚举 vs 常量枚举
| 对比维度 | 普通枚举 | 常量枚举 |
|---|---|---|
| 编译结果 | 生成真实对象(包含正向和反向映射) | 完全删除,值直接内联 |
| 运行时开销 | 有 | 无 |
| 计算成员支持 | 支持(如 Up = 1 << 1) |
不支持 |
| 反射访问 | 可通过 Object.keys() 访问 |
不可访问 |
| 适用场景 | 需要运行时动态访问枚举成员 | 性能敏感场景,无需动态访问 |
编译结果对比
// 普通枚举编译结果
enum Direction { Up = 0 }
// 转换为:
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
})(Direction || (Direction = {}));
// 常量枚举编译结果
const enum Direction { Up = 0 }
console.log(Direction.Up);
// 转换为:
console.log(0 /* Up */);
五、使用建议
| 场景 | 推荐类型 | 原因 |
|---|---|---|
| 需要根据值获取枚举名称 | 普通枚举 | 普通枚举生成反向映射,支持 Direction[0] → "Up"
|
| 性能敏感且无需动态访问 | 常量枚举 | 无运行时开销,适合高频调用的核心逻辑 |
| 需要计算成员或复杂初始化 | 普通枚举 | 常量枚举只能使用常量表达式 |
| 跨模块/库导出枚举 | 普通枚举 | 常量枚举无法被外部模块引用 |
| 减少打包体积 | 常量枚举 | 编译后删除,不生成额外代码 |
六、最佳实践
-
优先字符串枚举(除非需要数字运算):
enum LogLevel { Debug = "DEBUG", // 更清晰的调试信息 Error = "ERROR" } -
避免混合类型枚举:
// 不推荐 enum ConfusingEnum { A = 1, B = "B" } -
常量枚举命名约定:
const enum HTTP_METHODS { // 全大写+下划线 GET = "GET", POST = "POST" }
6. 可空类型处理
一、核心处理策略
1. 类型声明
-
联合类型:明确变量可能为
null或undefinedlet username: string | null = null; // 允许 null let age: number | undefined; // 允许 undefined
2. 开启严格检查
-
在tsconfig.json中启用严格模式:
{ "compilerOptions": { "strictNullChecks": true // 强制检查 null/undefined } }
二、安全访问与操作
1. 可选链操作符(Optional Chaining ?.)
处理嵌套对象中的潜在 null/undefined:
interface User {
profile?: {
address?: {
city: string;
};
};
}
const city = user.profile?.address?.city; // 类型推断为 string | undefined
2. 空值合并操作符(Nullish Coalescing ??)
提供默认值,仅当左侧为 null/undefined 时生效:
const displayName = inputName ?? "Anonymous"; // 若 inputName 为 null/undefined,默认值为 "Anonymous"
3. 类型守卫(Type Guards)
显式检查收窄类型范围:
function printLength(text: string | null) {
if (text !== null) {
console.log(text.length); // 类型收窄为 string
}
}
4. 非空断言(Non-null Assertion !)
慎用:仅在确保值非空时使用:
const root = document.getElementById("root")!; // 断言 root 元素存在
root.innerHTML = "Loaded";
三、函数与API设计
1. 函数参数处理
// 明确可选参数类型
function createUser(name: string, age?: number) { // age 自动包含 undefined
const finalAge = age ?? 18; // 默认值处理
}
2. 返回值处理
// 明确返回可能为 null 的值
function findUser(id: string): User | null {
// ...
}
3. 错误处理
// 安全解析 JSON
function safeParse(json: string): unknown | null {
try {
return JSON.parse(json);
} catch {
return null;
}
}
四、实用工具与模式
1. 自定义类型守卫
// 检查值是否有效
function isDefined<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
// 使用示例
const data: (string | null)[] = ["A", null, "B"];
const validData = data.filter(isDefined); // 类型收窄为 string[]
2. 工具类型
// 移除 null/undefined
type NonNullableUser = NonNullable<User | null>; // 等效于 User
五、实际场景示例
1. React 组件 Props 处理
interface Props {
title: string;
subtitle?: string; // 可选属性
}
const Header = ({ title, subtitle }: Props) => (
<div>
<h1>{title}</h1>
{subtitle && <h2>{subtitle}</h2>} // 安全渲染可选内容
</div>
);
2. API 响应处理
interface ApiResponse<T> {
data: T | null;
error: string | null;
}
async function fetchUser(id: string): ApiResponse<User> {
try {
const response = await axios.get(`/users/${id}`);
return { data: response.data, error: null };
} catch (err) {
return { data: null, error: err.message };
}
}
六、关键注意事项
避免
any:不要用any绕过类型检查,失去安全优势。谨慎断言:非空断言
!可能导致运行时错误,优先用可选链和类型守卫。-
语义区分:
-
null:表示“无值”(需主动赋值) -
undefined:表示“未初始化”(默认状态)
-
7. 联合类型 & 交叉类型
一、联合类型(Union Types)
1. 核心定义
- 用
|符号组合多个类型,表示值可以是其中任意一种类型。 -
本质:逻辑“或”关系(
T | U)。
2. 典型应用场景
- 函数参数支持多种类型
- 处理不确定类型的返回值(如 API 响应)
- 状态机中的互斥状态
3. 代码示例
// 基础用法
type ID = string | number;
function printID(id: ID) {
console.log(id);
}
printID("ABC123"); // 合法
printID(456); // 合法
// 类型守卫处理
type Shape =
{ kind: "circle"; radius: number } |
{ kind: "square"; size: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.radius ** 2;
case "square": return shape.size ** 2;
}
}
4. 实际项目案例
// API 响应数据
type ApiResponse<T> =
{ status: "success"; data: T } |
{ status: "error"; message: string };
// 处理登录结果
async function login(user: string, pwd: string): Promise<ApiResponse<User>> {
try {
const res = await axios.post("/login", { user, pwd });
return { status: "success", data: res.data };
} catch (err) {
return { status: "error", message: err.message };
}
}
二、交叉类型(Intersection Types)
1. 核心定义
- 用
&符号组合多个类型,表示值必须同时满足所有类型。 -
本质:逻辑“与”关系(
T & U)。
2. 典型应用场景
- 合并多个接口或对象类型
- Mixin 模式实现
- 扩展第三方库类型定义
3. 代码示例
// 合并对象属性
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged; // { name: string; age: number }
const alice: Person = {
name: "Alice",
age: 30
};
// 合并方法
type Loggable = { log: (msg: string) => void };
type Serializable = { serialize: () => string };
type Logger = Loggable & Serializable;
const logger: Logger = {
log: (msg) => console.log(msg),
serialize: () => JSON.stringify({})
};
4. 实际项目案例
// 扩展 React 组件 Props
type BaseProps = { className: string };
type Clickable = { onClick: () => void };
type ButtonProps = BaseProps & Clickable;
const Button = ({ className, onClick }: ButtonProps) => (
<button className={className} onClick={onClick}>Click</button>
);
// 混入工具函数
function extend<T, U>(first: T, second: U): T & U {
return { ...first, ...second };
}
const obj = extend(
{ a: 1 },
{ b: "2" }
); // 类型推断为 { a: number } & { b: string }
三、核心区别对比

冲突属性示例
// 交叉类型中的属性冲突
type A = { id: string };
type B = { id: number };
type Conflict = A & B;
// id 的类型为 string & number → never
const c: Conflict = { id: "123" }; // 错误:不能将 string 赋值给 never
四、进阶技巧
1. 联合类型与条件类型
type FilterString<T> = T extends string ? T : never;
type Result = FilterString<"a" | 1 | "b">; // "a" | "b"
2. 交叉类型与泛型约束
// 约束泛型必须同时满足多个接口
function clone<T extends Named & Serializable>(source: T): T {
return { ...source };
}
3. 类型工具结合
// 提取联合类型中的共有属性
type CommonKeys<T, U> = keyof (T & U);
五、最佳实践建议
-
优先使用联合类型:
- 当处理逻辑需要区分不同类型时(如状态切换)
- 参数支持多类型输入时
-
合理使用交叉类型:
- 合并多个接口时(如扩展第三方库类型)
- 实现混入(Mixin)模式时
-
避免类型冲突:
交叉类型中同名属性类型必须兼容
-
使用工具类型(如Omit)
解决冲突:
type SafeMerge<T, U> = T & Omit<U, keyof T>;
8. 声明文件(.d.ts)
一、声明文件的核心概念
1. 定义
文件扩展名:
.d.ts(如jquery.d.ts)核心作用:为现有的 JavaScript 代码(如第三方库、旧项目代码)提供 类型声明
-
** 常见场景**:
- 为无类型声明的 JS 库添加类型支持(如 jQuery、Lodash)
- 渐进式迁移 JS 项目到 TS
- 扩展已有库的类型定义
2. 核心价值
| 优势 | 说明 |
|---|---|
| 类型安全 | 为 JS 代码提供静态类型检查 |
| 智能提示 | 在 IDE 中获得代码补全和接口文档提示 |
| 生态兼容 | 无缝使用现有 JS 生态库 |
| 协作效率 | 明确定义接口契约,提升团队协作效率 |
二、声明文件的常见场景
1. 为第三方 JS 库添加类型
// jquery.d.ts
declare interface JQuery {
modal(action: 'show' | 'hide'): void;
datepicker(options: DatepickerOptions): void;
}
declare const $: {
(selector: string): JQuery;
ajax(url: string): Promise<any>;
};
2. 旧 JS 项目迁移
// legacy-utils.d.ts
declare module "legacy-utils" {
export function formatDate(date: Date): string;
export const version: string;
}
3. 扩展已有类型
// vue-extensions.d.ts
import Vue from 'vue';
declare module 'vue/types/vue' {
interface Vue {
$myPlugin: (msg: string) => void;
}
}
三、声明文件的创建与使用
1. 手动创建声明文件
// global.d.ts(全局声明)
declare const VERSION: string; // 声明全局常量
declare function log(message: string): void; // 声明全局函数
// 模块声明
declare module "my-module" {
export function calculate(a: number): number;
}
2. 自动生成声明文件
通过 tsconfig.json 配置自动生成:
{
"compilerOptions": {
"declaration": true, // 生成 .d.ts 文件
"declarationDir": "./types" // 输出目录
}
}
运行 tsc 后,所有 .ts 文件会生成对应的 .d.ts 文件。
3. 使用社区类型库
通过 npm 安装现成的类型声明:
npm install --save-dev @types/jquery
四、声明文件的语法详解
1. 全局声明
// 声明全局变量
declare const __DEV__: boolean;
// 声明全局函数
declare function parseJSON(json: string): any;
// 声明全局接口
interface Window {
myLib: {
version: string;
};
}
2. 模块声明
// 声明模块
declare module "*.css" {
const styles: { [className: string]: string };
export default styles;
}
// 声明第三方模块
declare module "old-js-library" {
export function deprecatedMethod(): void;
}
3. 类型扩展
// 扩展 Express 的 Request 类型
declare namespace Express {
interface Request {
user?: {
id: string;
role: string;
};
}
}
五、实际项目应用示例
1. 自定义工具库声明
// utils.d.ts
declare module "my-utils" {
export function uuid(): string;
export function formatPrice(amount: number): string;
}
// 使用
import { uuid } from "my-utils";
const id = uuid();
2. 为图表库添加类型
// echarts-plugin.d.ts
import * as echarts from 'echarts';
declare module 'echarts' {
interface ECharts {
setDarkTheme(): void;
registerMap(mapName: string, geoJSON: object): void;
}
}
六、最佳实践指南
| 场景 | 推荐方案 | 示例 |
|---|---|---|
| 使用第三方 JS 库 | 优先安装 @types/ 包 |
npm install --save-dev @types/lodash |
| 自定义全局变量 | 通过 global.d.ts 声明 |
declare const API_BASE: string; |
| 旧项目迁移 | 逐步添加 .d.ts 文件 |
为每个旧模块创建声明文件 |
| 扩展第三方库类型 | 使用模块补充声明 | 扩展 Vue/React 全局接口 |
| CSS Modules 支持 | 声明 *.module.css 文件类型 |
declare module "*.module.css" { ... } |
七、常见问题解决
1. 如何处理无类型声明的库?
-
方案一:快速添加基础类型声明
// my-untyped-lib.d.ts declare module "untyped-lib"; 方案二:编写完整类型声明(推荐长期使用)
2. 类型声明冲突怎么办?
- 检查
@types/包版本是否与库版本匹配 - 使用
// @ts-ignore暂时忽略特定错误(慎用)
3. 全局声明与模块声明混用
// 正确方式:全局声明与模块声明分开文件
// global.d.ts
declare const VERSION: string;
// my-module.d.ts
declare module "my-module" { ... }
9:命名空间(Namespace) vs 模块(Module)**
核心区别
| 对比维度 | 命名空间 (Namespace) | 模块 (Module) |
|---|---|---|
| 代码组织方式 | 通过逻辑分组管理全局作用域内的代码 | 以文件为单元隔离作用域 |
| 加载机制 | 需通过<script>标签手动引入 |
通过import/export自动解析依赖 |
| 适用场景 | 浏览器环境全局变量管理 | 现代模块化开发(Node.js、Webpack等) |
| 类型合并 | 支持同名命名空间自动合并 | 文件模块独立,不可合并 |
1. 命名空间(Namespace)
-
定义:通过
namespace关键字包裹的代码容器,用于避免全局污染 - 典型应用:传统浏览器环境下的SDK开发
// 定义命名空间
namespace MyUtils {
export function formatDate(date: Date): string {
return date.toISOString();
}
}
// 使用(需通过<script>引入)
MyUtils.formatDate(new Date());
-
实际场景:
旧版jQuery插件开发中,通过命名空间扩展功能:
// 扩展jQuery类型
declare namespace JQuery {
interface Instance {
modal(action: 'show' | 'hide'): void;
}
}
// 使用
$('#dialog').modal('show');
2. 模块(Module)
-
定义:每个
.ts文件即模块,通过export暴露接口,import引入依赖 - 典型应用:现代前端工程化项目
// math.ts(模块文件)
export function sum(a: number, b: number): number {
return a + b;
}
// app.ts(使用模块)
import { sum } from './math';
console.log(sum(1, 2)); // 3
-
实际场景:
React组件库开发中的模块化组织:
// components/Button.tsx
import React from 'react';
export interface ButtonProps {
type?: 'primary' | 'default';
}
export default function Button(props: ButtonProps) {
return <button className={props.type}>{props.children}</button>;
}
// App.tsx
import Button, { ButtonProps } from './components/Button';
3. 混合使用(历史遗留场景)
// 命名空间包裹模块(兼容旧系统)
namespace LegacySystem {
export module Auth {
export function login() { /*...*/ }
}
}
// 调用
LegacySystem.Auth.login();
选择建议
新项目:
统一使用模块(import/export),配合Webpack/Rollup等构建工具-
旧项目迁移:
逐步用模块替换命名空间,通过tsconfig.json设置:
{ "compilerOptions": { "module": "ESNext", // 模块系统 "moduleResolution": "Node" // 模块解析策略 } }
10. 类型断言(Type Assertion)
定义
类型断言是开发者显式告诉编译器某个值的具体类型的方式,类似于其他语言中的“类型转换”,但 不进行运行时检查,仅影响编译阶段的类型推断。
核心特点
- 不改变运行时数据:仅影响编译时的类型检查
- 使用场景:当开发者比编译器更清楚变量类型时(如 DOM 元素操作、第三方库类型不完整)
语法格式
// 尖括号语法(不推荐在 JSX 中使用)
const value1 = <Type>value;
// as 语法(通用)
const value2 = value as Type;
实际应用
// 1. DOM 元素操作
const input = document.getElementById("username") as HTMLInputElement;
input.value = "Alice";
// 2. 处理第三方库返回的不确定类型
const data = fetchData() as ApiResponse<User>;
// 3. 强制联合类型收窄
function process(value: string | number) {
const str = value as string; // 需确保运行时类型正确
}
注意事项
避免滥用:错误的断言可能导致运行时错误
-
双重断言:当直接断言不兼容时,需通过
as unknown as T中转
const element = document.body as unknown as React.Component;
11. 可选参数与默认参数
可选参数(Optional Parameters)
定义
函数参数可被省略,通过 ? 标记,类型自动联合 undefined。
语法
function greet(name: string, age?: number) {
console.log(`Name: ${name}, Age: ${age || "unknown"}`);
}
注意事项
必须位于参数列表末尾
-
需处理
undefined值:function log(message: string, prefix?: string) { const finalPrefix = prefix ?? "[INFO]"; // 空值合并处理 console.log(`${finalPrefix}: ${message}`); }
默认参数(Default Parameters)
定义
参数可设置默认值,调用时可省略,类型自动推断(无需联合 undefined)。
语法
function createUser(name: string, role: "user" | "admin" = "user") {
return { name, role };
}
注意事项
位置灵活:默认参数可位于参数列表任意位置
类型推断:默认值决定参数类型,无需显式声明
function multiply(a: number, b = 1) { // b 推断为 number
return a * b;
}
对比:可选参数 vs 默认参数
| 维度 | 可选参数 | 默认参数 | |
|---|---|---|---|
| 语法 | param?: type |
param: type = defaultValue |
|
| 类型 | `type | undefined` | |
| 参数位置 | 必须位于末尾 | 可位于任意位置 | |
| 默认值处理 | 需手动处理 undefined
|
自动填充默认值 | |
| 适用场景 | 参数可能完全缺失时 | 参数通常有基础默认值时 |
实际应用场景
1. React 组件 Props
interface ButtonProps {
text: string;
type?: "primary" | "secondary"; // 可选参数
disabled?: boolean;
}
const Button = ({ text, type = "primary" }: ButtonProps) => (
<button className={type}>{text}</button>
);
2. API 请求函数
async function fetchData(
endpoint: string,
method: "GET" | "POST" = "GET", // 默认参数
retryCount?: number // 可选参数
) {
// ...
}
项目应用:
- API错误类型区分处理
- 多态组件类型安全判断
// 错误处理
if (isNetworkError(err)) {
showToast("网络异常");
} else if (isBusinessError(err)) {
showToast(err.message);
}
12. 类型守卫(Type Guards)
定义
类型守卫是用于 缩小变量类型范围 的表达式或函数,通过运行时检查帮助 TypeScript 编译器确定更具体的类型。
核心作用
- 类型收窄:将联合类型缩小到具体类型
- 安全访问:避免访问不存在的属性或方法
- 智能提示:在代码块中获得精确的类型提示
常见类型守卫
1. typeof 守卫
function padLeft(value: string | number) {
if (typeof value === "number") {
return " ".repeat(value); // value 收窄为 number
}
return value; // value 收窄为 string
}
2. instanceof 守卫
class ApiError extends Error { code = 500 }
class NetworkError extends Error { code = 503 }
function handleError(err: Error) {
if (err instanceof ApiError) {
console.log(err.code); // 访问 ApiError 特有属性
}
}
3. in 守卫
interface Fish { swim(): void }
interface Bird { fly(): void }
function move(animal: Fish | Bird) {
if ("swim" in animal) {
animal.swim(); // 收窄为 Fish
}
}
4. 自定义类型守卫函数
function isFish(animal: Fish | Bird): animal is Fish {
return (animal as Fish).swim !== undefined;
}
// 使用
if (isFish(animal)) {
animal.swim();
}
实际应用场景
-
API 响应处理:
type ApiResponse = { status: "success"; data: User } | { status: "error"; code: number }; function handleResponse(res: ApiResponse) { if (res.status === "success") { console.log(res.data); // 安全访问 data } }
13. 索引类型(Index Types)
定义
索引类型是 TypeScript 中用于 动态访问对象属性 的类型工具,通过 keyof 和索引访问操作符 T[K] 实现类型安全操作。
核心组成
-
keyof T:获取类型T的所有属性键的联合类型 -
T[K]:通过键K访问T对应属性的类型
代码示例
interface User {
id: string;
name: string;
age: number;
}
// 获取所有键的联合类型
type UserKeys = keyof User; // "id" | "name" | "age"
// 动态访问属性类型
type UserNameType = User["name"]; // string
// 泛型约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: "1", name: "Alice", age: 30 };
const name = getProperty(user, "name"); // 类型推断为 string
核心优势
| 优势 | 说明 |
|---|---|
| 类型安全 | 避免属性名拼写错误或访问不存在的属性 |
| 代码复用 | 编写通用工具函数(如 pick, omit) |
| 自动同步 | 类型定义修改时,相关代码自动适配 |
| 智能提示 | IDE 自动补全可用属性名 |
实际应用场景
1. 动态表单验证器
function validate<T>(obj: T, rules: { [K in keyof T]?: (val: T[K]) => boolean }) {
Object.entries(rules).forEach(([key, validator]) => {
if (validator) validator(obj[key as keyof T]);
});
}
// 使用
validate(user, {
age: (val) => val >= 18 // val 自动推断为 number
});
2. 通用数据转换
type ReadonlyUser = { readonly [K in keyof User]: User[K] };
type OptionalUser = { [K in keyof User]?: User[K] };
// 将 User 所有属性转为可选
const partialUser: OptionalUser = { name: "Bob" };
3. ORM 字段过滤
function selectFields<T, K extends keyof T>(data: T, fields: K[]): Pick<T, K> {
return fields.reduce((res, key) => {
res[key] = data[key];
return res;
}, {} as Pick<T, K>);
}
// 使用
const userBasic = selectFields(user, ["id", "name"]); // { id: string; name: string }
总结对比
| 特性 | 类型守卫 | 索引类型 |
|---|---|---|
| 主要目的 | 运行时类型检查与收窄 | 动态安全访问对象属性 |
| 核心语法 |
typeof, instanceof, 自定义函数 |
keyof, T[K], 映射类型 |
| 典型应用 | 处理联合类型分支逻辑 | 构建通用工具函数和类型工具 |
| 类型安全 | 确保运行时类型正确 | 确保属性访问和操作的类型安全 |
14. const 和 readonly 的区别
| 对比维度 | const |
readonly |
|---|---|---|
| 作用对象 | 变量(基本类型或引用) | 类属性、接口属性、索引签名、数组类型 |
| 作用阶段 | 变量赋值(运行时不可重新赋值) | 属性修改(编译时检查) |
| 可变性 | 基本类型不可变,引用类型内部属性可变 | 完全不可变(包括对象属性) |
| 应用场景 | 声明常量(如配置值) | 定义不可变属性(如 React Props、DTO 模型) |
| 数组约束 | 无法约束数组内部修改 |
ReadonlyArray<T> 禁止增删改操作 |
代码示例
// const 示例
const PI = 3.14;
const obj = { a: 1 };
obj.a = 2; // 允许修改对象属性
// readonly 示例
interface User {
readonly id: string;
}
class Config {
constructor(public readonly apiUrl: string) {}
}
const user: User = { id: "123" };
user.id = "456"; // 编译错误:无法修改 readonly 属性
15. any 类型的作用与滥用后果
作用
- 快速原型开发:跳过类型检查,快速验证逻辑。
- 兼容旧代码:逐步迁移 JS 项目时处理无类型代码。
- 动态数据处理:处理高度动态的数据(如第三方 API 响应)。
滥用后果
| 问题 | 说明 |
|---|---|
| 类型安全丧失 | 编译器不检查类型错误,导致运行时崩溃风险 |
| 代码可读性差 | 团队协作时难以理解数据结构和意图 |
| 工具链失效 | IDE 智能提示和自动补全失效 |
| 技术债务积累 | 长期维护成本增加,重构困难 |
示例与替代方案
// 滥用 any(危险!)
function parseData(data: any) {
console.log(data.toUpperCase()); // 运行时可能出错
}
// 替代方案:使用 unknown 或类型守卫
function safeParse(data: unknown) {
if (typeof data === "string") {
console.log(data.toUpperCase());
}
}
16. TypeScript 中的 this 注意事项
核心问题
JavaScript 的 this 动态绑定机制可能导致意外行为,TypeScript 通过以下方式增强管理:
-
箭头函数:自动绑定外层
this。 -
显式类型注解:声明
this的预期类型。 -
编译选项:
noImplicitThis禁止隐式any类型的this。
注意事项
| 场景 | 问题 | 解决方案 |
|---|---|---|
类方法中的 this
|
方法作为回调时丢失 this 绑定 |
使用箭头函数或 bind
|
函数参数中的 this
|
需要明确 this 类型 |
显式注解(如 function(this: MyClass)) |
| 对象字面量方法 |
this 指向对象本身而非预期上下文 |
使用箭头函数 |
代码示例
class Timer {
constructor(public seconds: number) {}
// 错误:普通方法,this 可能丢失
start() {
setInterval(function() {
this.seconds--; // 运行时 this 指向全局对象
}, 1000);
}
// 正确:箭头函数绑定 this
startSafe = () => {
setInterval(() => {
this.seconds--; // this 正确指向实例
}, 1000);
}
}
// 显式 this 类型注解
function clickHandler(this: HTMLButtonElement) {
console.log(this.tagName); // 确保 this 是按钮元素
}
配置建议
在 tsconfig.json 中开启严格检查:
{
"compilerOptions": {
"strict": true,
"noImplicitThis": true
}
}
17. TypeScript数据类型
// 基础类型
let isDone: boolean = true;
let price: number = 99.5;
let name: string = "Alice";
// 特殊类型
let u: undefined = undefined;
let n: null = null;
let v: void = undefined;
// 引用类型
let ids: number[] = [1, 2, 3];
let user: { name: string } = { name: "Bob" };
18. interface 如何声明函数、数组和类?
(1) 函数类型声明
// 定义函数接口
interface SearchFunc {
(source: string, keyword: string): boolean;
}
// 实现函数
const mySearch: SearchFunc = (src, kw) => src.includes(kw);
(2) 数组/索引类型声明
// 定义数组接口
interface StringArray {
[index: number]: string;
}
// 使用
const arr: StringArray = ["Alice", "Bob"];
console.log(arr[0]); // "Alice"
// 定义字典对象
interface NumberDictionary {
[key: string]: number;
length: number; // 必须符合索引签名类型
name: string; // 错误:string 不符合 number 类型
}
(3) 类类型声明
// 定义类接口
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
// 类实现接口
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
}
19. 协变、逆变、双变与抗变
核心概念
| 术语 | 定义 | TypeScript 示例 |
|---|---|---|
| 协变 | 如果 A 是 B 的子类型,则 T<A> 是 T<B> 的子类型(保持方向) |
string 是 `string |
| 逆变 | 如果 A 是 B 的子类型,则 T<B> 是 T<A> 的子类型(反转方向) |
函数参数类型:(arg: Animal) => void 是 (arg: Dog) => void 的子类型 |
| 双变 | 同时允许协变和逆变 | TypeScript 默认函数参数为双变(可通过 strictFunctionTypes 禁用) |
| 抗变 | 类型必须完全一致(既不协变也不逆变) | 泛型默认行为:type Exact<T> = { value: T } → Exact<Dog> 不可赋值给 Exact<Animal>
|
实际影响
// 协变示例
type Animal = { name: string };
type Dog = Animal & { breed: string };
let animals: Animal[] = [];
let dogs: Dog[] = [{ name: "Buddy", breed: "Labrador" }];
animals = dogs; // 协变允许
// 逆变示例(函数参数)
type Handler = (arg: Dog) => void;
const animalHandler: (arg: Animal) => void = (animal) => console.log(animal.name);
const dogHandler: Handler = animalHandler; // 逆变允许(当开启 strictFunctionTypes 时不允许)
// 双变配置
// tsconfig.json 中设置 "strictFunctionTypes": false(默认允许双变)
20. 静态类型 vs 动态类型
核心区别
| 维度 | 静态类型(TypeScript) | 动态类型(JavaScript) |
|---|---|---|
| 类型检查时机 | 编译时 | 运行时 |
| 错误反馈 | 编码阶段提示类型错误 | 运行到具体代码时可能崩溃 |
| 代码可读性 | 类型注解作为文档,提升可维护性 | 需通过代码逻辑推断类型 |
| 性能影响 | 无运行时开销(类型信息在编译后删除) | 运行时动态类型检查可能轻微影响性能 |
| 工具支持 | IDE 智能提示、自动补全、重构支持 | 依赖代码结构和注释 |
代码示例
// TypeScript(静态类型)
function add(a: number, b: number): number {
return a + b;
}
add(1, "2"); // 编译报错:Argument of type 'string' is not assignable to parameter of type 'number'
// JavaScript(动态类型)
function add(a, b) {
return a + b;
}
add(1, "2"); // 运行时返回 "12"(可能非预期行为)
适用场景
| 场景 | 推荐类型 | 原因 |
|---|---|---|
| 大型企业级项目 | 静态类型 | 提升代码健壮性和团队协作效率 |
| 快速原型开发 | 动态类型 | 减少类型注解负担,快速迭代 |
| 长期维护系统 | 静态类型 | 类型系统帮助预防代码腐化 |
| 脚本/小型工具 | 动态类型 | 简单场景下类型注解可能过度设计 |
21. TypeScript 中的模块化是如何工作的,举例说明
1. 模块化核心机制
TypeScript 的模块化基于 ES Modules (ESM)* 规范,通过 import/export 语法实现,同时兼容 *CommonJS** 格式(通过编译选项控制)。
工作流程:
-
声明导出:使用
export暴露接口 -
导入使用:通过
import引入依赖 -
编译处理:根据
tsconfig.json中的module配置生成对应模块格式代码
2. 模块化 vs 命名空间
| 特性 | 模块化 | 命名空间 |
|---|---|---|
| 作用域 | 文件级作用域 | 全局/命名空间内部作用域 |
| 加载方式 | 动态加载(浏览器需支持ESM或打包工具) |
<script> 标签同步加载 |
| 依赖管理 | 显式声明依赖关系 | 隐式依赖(需手动管理加载顺序) |
| 适用场景 | 现代前端工程、Node.js | 传统浏览器脚本 |
3. 模块化代码示例
(1) 基础导出/导入
// math.ts(模块文件)
export function add(a: number, b: number): number {
return a + b;
}
export const PI = 3.1415;
// app.ts(使用模块)
import { add, PI } from './math';
console.log(add(1, 2), PI); // 3, 3.1415
(2) 默认导出
// logger.ts
export default class Logger {
static log(message: string) {
console.log(`[INFO] ${message}`);
}
}
// app.ts
import Logger from './logger';
Logger.log('Module loaded');
(3) 混合导出
// utils.ts
export function formatDate() { /*...*/ }
export default function validate() { /*...*/ }
// app.ts
import validate, { formatDate } from './utils';
4. 实际项目应用
(1) React 组件模块化
// components/Button.tsx
import React from 'react';
export interface ButtonProps {
type?: 'primary' | 'default';
onClick: () => void;
}
export default function Button({ type = 'default', onClick }: ButtonProps) {
return <button className={type} onClick={onClick}>Click</button>;
}
// App.tsx
import Button, { ButtonProps } from './components/Button';
const handleClick = () => console.log('Clicked');
const buttonProps: ButtonProps = {
onClick: handleClick,
type: 'primary'
};
(2) Node.js 服务模块化
// config.ts
export interface ServerConfig {
port: number;
env: 'dev' | 'prod';
}
export const config: ServerConfig = {
port: 3000,
env: process.env.NODE_ENV === 'production' ? 'prod' : 'dev'
};
// server.ts
import { config, ServerConfig } from './config';
console.log(`Starting server on port ${config.port}`);
5. 编译配置与工具链
(1) tsconfig.json 配置
{
"compilerOptions": {
"module": "ESNext", // 输出模块格式(ESM/CommonJS等)
"moduleResolution": "Node", // 模块解析策略
"baseUrl": "./src", // 基础路径
"paths": { // 路径别名
"@utils/*": ["utils/*"]
}
}
}
(2) 构建工具集成
- Webpack/Rollup:处理模块依赖树和打包
- Babel:转换新语法(可选)
-
Tree Shaking:通过
"module": "ESNext"启用无用代码删除
6. 常见问题与解决方案
(1) 循环引用
// 模块A.ts
import { B } from './B';
export class A { /*...*/ }
// 模块B.ts
import { A } from './A'; // 循环引用警告
export class B { /*...*/ }
// 解决方案:使用动态导入或重构代码结构
(2) 类型声明合并
// types.d.ts(全局类型声明)
declare module "*.css" {
const styles: { [className: string]: string };
export default styles;
}
// App.tsx
import styles from './App.css'; // 支持CSS模块类型
模块化核心优势
- 依赖清晰化:显式声明依赖关系
- 作用域隔离:避免全局污染
- 按需加载:动态导入优化性能
- 工程友好:支持代码拆分和Tree Shaking
22. 如何约束泛型参数的类型范围
核心方法
使用 extends 关键字限制泛型参数必须满足特定条件,确保类型安全。
语法与示例
// 约束泛型 T 必须包含 length 属性
function logLength<T extends { length: number }>(arg: T): void {
console.log(arg.length);
}
logLength("hello"); // 5(字符串有 length)
logLength([1, 2, 3]); // 3(数组有 length)
logLength(42); // 错误:number 没有 length 属性
实际应用场景
- ** API 响应约束**:
确保返回数据符合特定结构interface ApiResponse<T> { code: number; data: T; } function handleResponse<T extends { id: string }>(res: ApiResponse<T>) { console.log(res.data.id); }
23. 泛型约束中的 keyof 关键字
定义
keyof T 获取类型 T 的所有 属性键的联合类型,常用于约束泛型参数必须为对象的有效属性名。
代码示例
// 确保 key 是对象 T 的合法属性名
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
interface User {
name: string;
age: number;
}
const user: User = { name: "Alice", age: 30 };
const name = getProperty(user, "name"); // string
const email = getProperty(user, "email"); // 错误:"email" 不是 User 的属性
实际应用场景
-
动态表单验证:
function validateField<T>(obj: T, field: keyof T) { if (!obj[field]) throw Error(`${String(field)} 必填`); }
24. 条件类型(Conditional Types)
定义
条件类型根据类型关系 动态决定最终类型,语法为 T extends U ? X : Y。
核心语法
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<123>; // false
实际应用场景
1. 类型工具库
// 实现内置的 Exclude 类型
type MyExclude<T, U> = T extends U ? never : T;
type Result = MyExclude<"a" | "b" | "c", "a">; // "b" | "c"
2. 处理可选字段
// 将可选属性转为必选并添加 undefined 类型
type RequiredWithUndefined<T> = {
[K in keyof T]-?: T[K] extends infer V | undefined ? V : T[K];
};
interface Props {
name?: string;
age?: number;
}
type StrictProps = RequiredWithUndefined<Props>;
// { name: string; age: number }
3. 递归条件类型
// 展开嵌套 Promise
type UnwrapPromise<T> = T extends Promise<infer U>
? UnwrapPromise<U>
: T;
type DeepPromise = Promise<Promise<number>>;
type Unwrapped = UnwrapPromise<DeepPromise>; // number
4. 分布式条件类型
// 过滤出函数类型
type FilterFunctions<T> = T extends (...args: any[]) => any ? T : never;
type MixedTypes = string | (() => void) | number;
type OnlyFuncs = FilterFunctions<MixedTypes>; // () => void
总结
| 概念 | 核心作用 | 典型场景 |
|---|---|---|
| 泛型约束 | 限制泛型参数类型范围 | 确保输入数据符合特定结构 |
keyof |
安全访问对象属性 | 动态属性操作、类型安全映射 |
| 条件类型 | 根据类型关系动态推导类型 | 类型工具开发、复杂类型转换 |
25. 什么是装饰器,有什么作用,如何在TypeScript中使用类装饰器
1. 装饰器(Decorator)基础
定义
装饰器是一种特殊类型的声明,通过 @expression 语法附加到 类、方法、访问器、属性或参数 上,用于修改或扩展目标的行为。
核心作用
- 功能扩展:在不修改原始代码的前提下添加新功能
- 元数据注入:为类或方法附加配置信息(如Angular中的@Component)
- AOP编程:实现日志、权限校验等横切关注点
- 代码复用:通过装饰器工厂创建可复用逻辑
2. 类装饰器(Class Decorator)
基本结构
// 装饰器定义
function ClassDecorator(target: Function) {
// 修改或扩展类行为
}
// 使用装饰器
@ClassDecorator
class MyClass {}
参数说明
- target:被装饰的类的构造函数
- 返回值:可返回新的构造函数来替换原始类
3. 类装饰器实战示例
(1) 基础用法 - 添加元数据
// 定义装饰器
function Table(name: string) {
return function (constructor: Function) {
constructor.prototype.tableName = name;
};
}
// 使用装饰器
@Table("users")
class UserModel {}
console.log((new UserModel() as any).tableName); // "users"
(2) 扩展功能 - 自动注册服务
// 服务容器
const serviceRegistry = new Map<string, any>();
function Service(key: string) {
return function <T extends { new (...args: any[]): {} }>(target: T) {
serviceRegistry.set(key, new target());
return target;
};
}
// 使用装饰器
@Service("auth")
class AuthService {
login() { /*...*/ }
}
// 获取实例
const auth = serviceRegistry.get("auth");
auth.login();
(3) 修改类 - 添加日志功能
function LogClass(target: any) {
return class extends target {
constructor(...args: any[]) {
super(...args);
console.log(`[LOG] ${target.name} instance created`);
}
};
}
@LogClass
class DataService {
fetchData() { /*...*/ }
}
new DataService(); // 输出: "[LOG] DataService instance created"
4. 装饰器工厂(Decorator Factory)
通过高阶函数创建可配置的装饰器:
// 工厂函数
function Prefix(prefix: string) {
return function (target: Function) {
target.prototype.prefix = prefix;
};
}
// 使用配置化装饰器
@Prefix("user_")
class IDGenerator {
generate() {
return this.prefix + Math.random().toString(36).slice(2);
}
}
const gen = new IDGenerator() as any;
console.log(gen.generate()); // "user_5q2g7x"
5. 实际应用场景
(1) ORM框架模型定义
@Model("articles")
class Article {
@Column({ type: "string" })
title!: string;
@Column({ type: "date" })
createdAt!: Date;
}
(2) 权限控制
@RequireRole("admin")
class AdminDashboard {
@AuditLog
deleteUser(id: string) { /*...*/ }
}
(3) 性能监控
@ProfilePerformance
class DataProcessor {
@MeasureExecutionTime
processLargeData() { /*...*/ }
}
6. 配置启用装饰器
在 tsconfig.json 中启用实验性装饰器支持:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
7. 注意事项
-
执行顺序:
- 多个装饰器从下到上执行(最接近类的装饰器最后执行)
@A() @B() class MyClass {} // 先执行B,再执行A -
元数据限制:
- 需配合
reflect-metadata库实现完整元数据反射
- 需配合
-
浏览器支持:
- 需通过Babel或TypeScript编译为ES5代码
总结
| 维度 | 类装饰器特性 |
|---|---|
| 修改能力 | 可添加/修改类属性和原型方法 |
| 替换能力 | 可返回新的构造函数完全替换原始类 |
| 应用层级 | 类级别(非实例级别) |
| 适用场景 | 全局配置、服务注册、元数据标记 |
26. 装饰器执行顺序
执行顺序规则
-
实例成员装饰器:
- 方法装饰器 → 属性装饰器 → 访问器装饰器 → 参数装饰器
- 执行顺序:从代码书写顺序的 从上到下 执行。
-
静态成员装饰器:
- 方法装饰器 → 属性装饰器 → 访问器装饰器 → 参数装饰器
- 执行顺序:同样从上到下。
-
类装饰器:
- 最后执行,且从代码书写顺序的 从下到上(若多个类装饰器)。
代码示例
// 类装饰器
function ClassDecorator(name: string) {
console.log(`类装饰器 ${name} 执行`);
return (target: any) => {};
}
// 方法装饰器
function MethodDecorator(name: string) {
console.log(`方法装饰器 ${name} 执行`);
return (target: any, key: string, descriptor: PropertyDescriptor) => {};
}
// 静态方法装饰器
function StaticMethodDecorator(name: string) {
console.log(`静态方法装饰器 ${name} 执行`);
return (target: any, key: string, descriptor: PropertyDescriptor) => {};
}
@ClassDecorator("A")
@ClassDecorator("B")
class MyClass {
@MethodDecorator("实例方法1")
method1() {}
@MethodDecorator("实例方法2")
method2() {}
@StaticMethodDecorator("静态方法1")
static staticMethod1() {}
@StaticMethodDecorator("静态方法2")
static staticMethod2() {}
}
输出顺序
方法装饰器 实例方法1 执行
方法装饰器 实例方法2 执行
静态方法装饰器 静态方法1 执行
静态方法装饰器 静态方法2 执行
类装饰器 B 执行
类装饰器 A 执行
关键规则总结
| 装饰器类型 | 执行顺序 |
|---|---|
| 实例方法装饰器 | 从上到下执行,先于类装饰器 |
| 静态方法装饰器 | 在实例方法装饰器之后、类装饰器之前执行 |
| 类装饰器 | 最后执行,且多个装饰器从下到上(装饰器工厂的调用顺序) |
实际应用场景
1. 元数据收集
当类装饰器需要读取方法装饰器设置的元数据时,需确保方法装饰器已执行:
// 方法装饰器:收集路由信息
function Route(path: string) {
return (target: any, key: string) => {
Reflect.defineMetadata("route", path, target, key);
};
}
// 类装饰器:统一注册路由
function Controller(prefix: string) {
return (target: any) => {
const methods = Object.getOwnPropertyNames(target.prototype);
methods.forEach(method => {
const path = Reflect.getMetadata("route", target.prototype, method);
if (path) registerRoute(`${prefix}${path}`, method);
});
};
}
@Controller("/api")
class UserController {
@Route("/users")
getUsers() {}
}
2. 依赖注入
类装饰器可能需要基于方法参数装饰器的信息初始化依赖:
// 参数装饰器标记依赖
function Inject(token: string) {
return (target: any, key: string, index: number) => {
Reflect.defineMetadata("inject", token, target, `param-${index}`);
};
}
// 类装饰器解析依赖
function Injectable() {
return (target: any) => {
const params = Reflect.getMetadata("design:paramtypes", target) || [];
const injections = params.map((param: any, index: number) => {
return Reflect.getMetadata("inject", target, `param-${index}`) || param;
});
Container.register(target, injections);
};
}
@Injectable()
class UserService {
constructor(@Inject("Logger") private logger: Logger) {}
}
注意事项
-
装饰器工厂顺序:
- 类装饰器工厂的调用顺序是 从上到下,但装饰器函数执行顺序是 从下到上。
-
元数据顺序:
- 若类装饰器依赖方法装饰器的元数据,需确保方法装饰器已通过
Reflect.defineMetadata写入数据。
- 若类装饰器依赖方法装饰器的元数据,需确保方法装饰器已通过
-
编译配置:
- 确保tsconfig.json启用装饰器支持:
{ "compilerOptions": { "experimentalDecorators": true } }
- 确保tsconfig.json启用装饰器支持:
掌握装饰器执行顺序,能更好地设计插件化架构和元数据驱动型应用。
27. 实现 sleep 方法
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 使用示例
async function demo() {
console.log("开始等待");
await sleep(2000); // 等待 2 秒
console.log("等待结束");
}
28. TypeScript 中的 is 关键字
is 是 类型谓词(Type Predicate),用于函数返回值中,帮助 TypeScript 收窄类型范围。
function isString(value: unknown): value is string {
return typeof value === "string";
}
function process(input: string | number) {
if (isString(input)) {
input.toUpperCase(); // 类型收窄为 string
}
}
29. TypeScript 支持的访问修饰符
| 修饰符 | 作用 |
|---|---|
public |
默认,任意位置可访问 |
private |
仅类内部可访问 |
protected |
类内部和子类可访问 |
readonly |
只读属性,初始化后不可修改 |
30. 实现 myMap 方法
function myMap<T, U>(arr: T[], callback: (item: T, index: number) => U): U[] {
const result: U[] = [];
for (let i = 0; i < arr.length; i++) {
result.push(callback(arr[i], i));
}
return result;
}
// 使用示例
const doubled = myMap([1, 2, 3], num => num * 2); // [2, 4, 6]
31. 实现 treePath 方法
假设树节点结构为 { id: number; children: TreeNode[] },找到从根到目标节点的路径:
interface TreeNode {
id: number;
children: TreeNode[];
}
function treePath(root: TreeNode, targetId: number): number[] | null {
const path: number[] = [];
const find = (node: TreeNode): boolean => {
path.push(node.id);
if (node.id === targetId) return true;
for (const child of node.children) {
if (find(child)) return true;
}
path.pop();
return false;
};
return find(root) ? path : null;
}
32. 实现 product 方法
function product(arr: number[]): number {
return arr.reduce((acc, num) => acc * num, 1);
}
// 使用示例
console.log(product([2, 3, 4])); // 24
33. 实现 myAll 方法(类似 Promise.all)
function myAll<T>(promises: Promise<T>[]): Promise<T[]> {
return new Promise((resolve, reject) => {
const results: T[] = [];
let completed = 0;
promises.forEach((promise, index) => {
promise.then(value => {
results[index] = value;
completed++;
if (completed === promises.length) resolve(results);
}).catch(reject);
});
});
}
34. 实现 sum 方法
function sum(arr: number[]): number {
return arr.reduce((acc, num) => acc + num, 0);
}
// 使用示例
console.log(sum([1, 2, 3])); // 6
35. 实现 mergeArray 方法
合并两个数组并去重:
function mergeArray<T>(arr1: T[], arr2: T[]): T[] {
return [...new Set([...arr1, ...arr2])];
}
// 使用示例
console.log(mergeArray([1, 2], [2, 3])); // [1, 2, 3]
36. 实现 firstSingleChar 方法
function firstSingleChar(str: string): string | null {
const countMap: Record<string, number> = {};
for (const char of str) {
countMap[char] = (countMap[char] || 0) + 1;
}
for (const char of str) {
if (countMap[char] === 1) return char;
}
return null;
}
// 使用示例
console.log(firstSingleChar("abaccdeff")); // "b"
37. 实现 reverseWord 方法
反转字符串中的单词顺序:
function reverseWord(s: string): string {
return s.trim().split(/\s+/).reverse().join(" ");
}
// 使用示例
console.log(reverseWord("hello world")); // "world hello"
38. 定义联合类型数组
// 元素可以是 string 或 number 类型
const mixedArray: (string | number)[] = [1, "two", 3, "four"];
39. 补充 objToArray 函数
// 将对象转换为键值对数组
function objToArray<T extends Record<string, any>>(obj: T): [string, any][] {
return Object.entries(obj);
}
// 使用示例
const obj = { a: 1, b: "2" };
const arr = objToArray(obj); // [ ["a", 1], ["b", "2"] ]
40. 判断数组类型的方法
// 方法一:使用类型守卫
function isArray(arg: unknown): arg is any[] {
return Array.isArray(arg);
}
// 方法二:泛型类型检查
const isArrayType = <T>(arg: T[] | unknown): arg is T[] => {
return Array.isArray(arg);
};
// 使用示例
console.log(isArray([1, 2])); // true
console.log(isArrayType<string>(["a"])); // true
41. TypeScript 内置数据类型
| 类型 | 示例 |
|---|---|
| 基本类型 |
string, number, boolean, null, undefined, symbol, bigint
|
| 特殊类型 |
any, unknown, never, void
|
| 对象类型 |
object, Array<T>, Tuple, enum
|
| 函数类型 | (param: T) => U |
42. any vs unknown
| 特性 | any |
unknown |
|---|---|---|
| 类型检查 | 禁用类型检查 | 必须显式类型检查或转换后才能使用 |
| 安全性 | 不安全,易导致运行时错误 | 安全,强制开发者处理类型 |
| 适用场景 | 兼容旧代码或快速原型开发 | 处理未知类型数据 |
43. 将 unknown 转换为具体类型
// 方法一:类型断言
const value = unknownValue as string;
// 方法二:类型守卫
if (typeof unknownValue === "string") {
console.log(unknownValue.toUpperCase());
}
// 方法三:类型推断(泛型)
function parse<T>(input: unknown): T {
return input as T;
}
44. tsconfig.json 的作用
编译配置:指定 TS 编译选项(如目标版本、模块系统、严格模式等)
工程定义:定义项目根目录和包含/排除文件
-
示例配置项:
{ "compilerOptions": { "target": "ES6", "module": "CommonJS", "strict": true, "outDir": "./dist" }, "include": ["src/**/*"], "exclude": ["node_modules"] }
45. declare 关键字的作用
声明全局类型:告诉 TS 编译器某个变量、函数或模块存在,但不实现它
-
典型场景:
// 声明全局变量 declare const __VERSION__: string; // 声明模块类型 declare module "*.png" { const path: string; export default path; } // 声明第三方库类型 declare module "jquery" { function $(selector: string): any; export = $; }
46. TypeScript 中 never 和 void 的区别
| 特性 | never |
void |
|---|---|---|
| 含义 | 表示函数永远不会返回(无法正常结束执行)。 | 表示函数没有返回值(返回 undefined)。 |
| 使用场景 | 函数抛出异常、死循环等不可达代码路径。 | 函数正常执行但没有返回有效值。 |
| 返回值 | 无返回值(不可到达的终点)。 | 返回 undefined。 |
| 类型兼容性 | 是所有类型的子类型(可赋值给任意类型)。 | 表示“无返回值”,与 undefined 兼容。 |
代码示例:
// never 类型
function throwError(msg: string): never {
throw new Error(msg);
}
function infiniteLoop(): never {
while (true) {}
}
// void 类型
function logMessage(msg: string): void {
console.log(msg);
// 隐式返回 undefined
}
47. TypeScript 中 interface 和 type 的差别
| 特性 | interface |
type(类型别名) |
|---|---|---|
| 扩展方式 | 通过 extends 继承。 |
通过 &(交叉类型)合并。 |
| 合并声明 | 支持同名接口自动合并。 | 同一作用域内不可重复定义。 |
| 适用类型 | 主要用于定义对象、类或函数的形状。 | 可定义任意类型(联合、交叉、元组、原始类型等)。 |
| 复杂类型支持 | 无法直接定义联合类型、元组等。 | 支持联合类型、交叉类型、条件类型、映射类型等。 |
| 实现(implements) | 类可通过 implements 实现接口。 |
无法直接被类实现(除非定义为对象类型)。 |
代码示例:
// interface 示例
interface Animal {
name: string;
}
interface Dog extends Animal { // 继承
breed: string;
}
// type 示例
type Status = "success" | "error"; // 联合类型
type Coordinates = [number, number]; // 元组类型
type PartialUser = { // 映射类型
[K in keyof User]?: User[K];
};
// interface 合并
interface User {
name: string;
}
interface User { // 合并后 User 包含 name 和 age
age: number;
}
// type 无法合并
type Person = { name: string };
type Person = { age: number }; // 报错:重复定义
使用建议:
-
优先使用
interface:
当需要定义对象的形状、类的契约或需要合并/扩展类型时(如组件 Props、API 响应结构)。 -
优先使用
type:
当需要定义联合类型、交叉类型、元组,或复杂工具类型(如Partial<T>、Pick<T, K>)时。
48 解释如何使用TypeScript mixin.
1. 定义 Mixin 函数
每个 Mixin 是一个函数,接收一个基类(Base),并返回一个扩展了该基类的新类,添加新的属性和方法。
// Mixin 1: 添加时间戳功能
type Constructor<T = {}> = new (...args: any[]) => T;
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
timestamp = Date.now();
};
}
// Mixin 2: 添加冻结功能
function Frozen<TBase extends Constructor>(Base: TBase) {
return class extends Base {
freeze() {
console.log("Object is frozen.");
}
};
}
-
Constructor类型:泛型构造函数类型,允许传递任意基类。 -
TBase extends Constructor:确保传入的是一个类构造函数。
2. 组合多个 Mixin
通过工具函数将多个 Mixin 应用到基类上。
// 组合多个 Mixin
function applyMixins(Base: any, mixins: any[]) {
return mixins.reduce((Class, mixin) => mixin(Class), Base);
}
// 基类
class Animal {
constructor(public name: string) {}
}
// 应用 Mixin:Animal → Timestamped → Frozen
const FrozenTimestampedAnimal = applyMixins(Animal, [Timestamped, Frozen]);
3. 使用组合后的类
实例化新类,验证 Mixin 功能。
// 实例化组合后的类
const myPet = new FrozenTimestampedAnimal("Buddy");
console.log(myPet.name); // "Buddy"(来自基类 Animal)
console.log(myPet.timestamp); // 时间戳(来自 Mixin Timestamped)
myPet.freeze(); // "Object is frozen."(来自 Mixin Frozen)
4. 处理构造函数参数
确保每个 Mixin 正确处理基类的构造函数参数。
// 带有参数的 Mixin
function Named<TBase extends Constructor>(Base: TBase) {
return class extends Base {
private _name: string;
constructor(...args: any[]) {
super(...args);
this._name = args[0]; // 假设第一个参数是名称
}
getName() {
return this._name;
}
};
}
// 应用 Mixin
class Person extends applyMixins(Object, [Named]) {
constructor(name: string) {
super(name);
}
}
const user = new Person("Alice");
console.log(user.getName()); // "Alice"
5. 类型定义与接口合并
使用接口合并描述 Mixin 添加的方法,确保类型安全。
// 定义接口描述 Mixin 添加的属性和方法
interface Frozen {
freeze(): void;
}
interface Timestamped {
timestamp: number;
}
// 应用 Mixin 后的类类型
type FrozenTimestampedAnimal = Animal & Frozen & Timestamped;
const myPet: FrozenTimestampedAnimal = new FrozenTimestampedAnimal("Buddy");
myPet.freeze(); // 类型安全:可调用 freeze 方法
6. 处理名称冲突
如果多个 Mixin 有同名方法或属性,需手动解决冲突。
function Walker<TBase extends Constructor>(Base: TBase) {
return class extends Base {
walk() {
console.log("Walking...");
}
};
}
function Runner<TBase extends Constructor>(Base: TBase) {
return class extends Base {
run() {
console.log("Running...");
}
};
}
// 组合时注意顺序:后应用的 Mixin 会覆盖同名成员
const ActiveAnimal = applyMixins(Animal, [Walker, Runner]);
const activePet = new ActiveAnimal("Rex");
activePet.walk();
activePet.run();