前端面试题---typescript面试题

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 后运行(tscbabel 直接解析执行
类型检查 编译时静态检查 运行时动态判断
错误反馈 编码阶段提示类型问题 运行到具体代码才报错
适用规模 中大型项目、长期维护系统 小型脚本、快速原型开发

四、项目应用场景
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 } }

优势

  • 输入输出类型自动关联
  • 泛型保证返回值类型一致性

五、核心优势总结
  1. 错误预防:编译阶段拦截 15% 以上潜在运行时错误(根据 Microsoft 统计数据)
  2. 代码可读性:类型注解作为“活文档”提升团队协作效率
  3. 重构安全:类型系统辅助安全的重命名、参数修改等操作
  4. 生态兼容:通过 .d.ts 声明文件支持所有 JavaScript 库(如 lodash、jQuery)
  5. 渐进式迁移:支持从 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"
性能敏感且无需动态访问 常量枚举 无运行时开销,适合高频调用的核心逻辑
需要计算成员或复杂初始化 普通枚举 常量枚举只能使用常量表达式
跨模块/库导出枚举 普通枚举 常量枚举无法被外部模块引用
减少打包体积 常量枚举 编译后删除,不生成额外代码

六、最佳实践

  1. 优先字符串枚举(除非需要数字运算):

    enum LogLevel {
      Debug = "DEBUG",  // 更清晰的调试信息
      Error = "ERROR"
    }
    
  2. 避免混合类型枚举

    // 不推荐
    enum ConfusingEnum {
      A = 1,
      B = "B"
    }
    
  3. 常量枚举命名约定

    const enum HTTP_METHODS { // 全大写+下划线
      GET = "GET",
      POST = "POST"
    }
    

6. 可空类型处理

一、核心处理策略

1. 类型声明
  • 联合类型:明确变量可能为

    null或undefined
    
    let 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 };
  }
}

六、关键注意事项

  1. 避免 any:不要用 any 绕过类型检查,失去安全优势。

  2. 谨慎断言:非空断言 ! 可能导致运行时错误,优先用可选链和类型守卫。

  3. 语义区分

    • 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 }

三、核心区别对比

image.png
冲突属性示例
// 交叉类型中的属性冲突
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);

五、最佳实践建议

  1. 优先使用联合类型

    • 当处理逻辑需要区分不同类型时(如状态切换)
    • 参数支持多类型输入时
  2. 合理使用交叉类型

    • 合并多个接口时(如扩展第三方库类型)
    • 实现混入(Mixin)模式时
  3. 避免类型冲突

    • 交叉类型中同名属性类型必须兼容

    • 使用工具类型(如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();

选择建议
  1. 新项目
    统一使用模块(import/export),配合Webpack/Rollup等构建工具

  2. 旧项目迁移
    逐步用模块替换命名空间,通过

    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. constreadonly 的区别

对比维度 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 通过以下方式增强管理:

  1. 箭头函数:自动绑定外层 this
  2. 显式类型注解:声明 this 的预期类型。
  3. 编译选项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 示例
协变 如果 AB 的子类型,则 T<A>T<B> 的子类型(保持方向) string 是 `string
逆变 如果 AB 的子类型,则 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** 格式(通过编译选项控制)。
工作流程

  1. 声明导出:使用 export 暴露接口
  2. 导入使用:通过 import 引入依赖
  3. 编译处理:根据 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模块类型

模块化核心优势

  1. 依赖清晰化:显式声明依赖关系
  2. 作用域隔离:避免全局污染
  3. 按需加载:动态导入优化性能
  4. 工程友好:支持代码拆分和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 语法附加到 类、方法、访问器、属性或参数 上,用于修改或扩展目标的行为。

核心作用

  1. 功能扩展:在不修改原始代码的前提下添加新功能
  2. 元数据注入:为类或方法附加配置信息(如Angular中的@Component)
  3. AOP编程:实现日志、权限校验等横切关注点
  4. 代码复用:通过装饰器工厂创建可复用逻辑

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. 注意事项

  1. 执行顺序

    • 多个装饰器从下到上执行(最接近类的装饰器最后执行)
    @A()
    @B()
    class MyClass {} // 先执行B,再执行A
    
  2. 元数据限制

    • 需配合 reflect-metadata 库实现完整元数据反射
  3. 浏览器支持

    • 需通过Babel或TypeScript编译为ES5代码

总结

维度 类装饰器特性
修改能力 可添加/修改类属性和原型方法
替换能力 可返回新的构造函数完全替换原始类
应用层级 类级别(非实例级别)
适用场景 全局配置、服务注册、元数据标记

26. 装饰器执行顺序

执行顺序规则

  1. 实例成员装饰器
    • 方法装饰器 → 属性装饰器 → 访问器装饰器 → 参数装饰器
    • 执行顺序:从代码书写顺序的 从上到下 执行。
  2. 静态成员装饰器
    • 方法装饰器 → 属性装饰器 → 访问器装饰器 → 参数装饰器
    • 执行顺序:同样从上到下。
  3. 类装饰器
    • 最后执行,且从代码书写顺序的 从下到上(若多个类装饰器)。

代码示例

// 类装饰器
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) {}
}

注意事项

  1. 装饰器工厂顺序

    • 类装饰器工厂的调用顺序是 从上到下,但装饰器函数执行顺序是 从下到上
  2. 元数据顺序

    • 若类装饰器依赖方法装饰器的元数据,需确保方法装饰器已通过 Reflect.defineMetadata 写入数据。
  3. 编译配置

    • 确保tsconfig.json启用装饰器支持:
      {
        "compilerOptions": {
          "experimentalDecorators": true
        }
      }
      

掌握装饰器执行顺序,能更好地设计插件化架构和元数据驱动型应用。


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 中 nevervoid 的区别

特性 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 中 interfacetype 的差别

特性 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();
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容