前端面试题---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 后运行(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 } }

优势:

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

五、核心优势总结
  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. 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 通过以下方式增强管理:

  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 示例
协变 如果 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** 格式(通过编译选项控制)。
工作流程:

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

相关阅读更多精彩内容

友情链接更多精彩内容