跳到主要内容
预计阅读 52 分钟

工程化篇 | TypeScript

前言

TypeScript 已经从”可选项”变成了前端项目的”标配”。据 2024 年 State of JS 调查,超过 80% 的开发者在日常工作中使用 TypeScript。Vue 3 用 TypeScript 重写了,Angular 从一开始就基于 TypeScript,React 社区也早已全面拥抱 TypeScript。

但很多人对 TypeScript 的理解还停留在”给变量加个类型注解”的阶段。面试中被问到 TypeScript,常见的卡壳场景有:

  • interfacetype 到底该用哪个?有什么区别?
  • 泛型是什么?什么场景下需要用泛型?
  • PartialPickOmitRecord 这些工具类型是怎么实现的?
  • tsconfig.json 里那么多选项,哪些是关键的?
  • 声明文件(.d.ts)是做什么的?怎么写?

本章,我们从 TypeScript 类型系统的核心概念讲起,梳理工具类型、类型体操入门、tsconfig 关键配置、与 ESLint 的集成,最后聊聊声明文件。


诊断自测

Q1:interfacetype 有什么核心区别?什么时候用哪个?

点击查看答案

核心区别:

  1. interface 支持声明合并(Declaration Merging),type 不支持——同名 interface 会自动合并,同名 type 会报错
  2. type 支持更多类型表达——联合类型、交叉类型、元组类型、条件类型等只能用 type 定义
  3. interface 只能描述对象形状,type 可以给任何类型起别名

实践建议:描述对象/类的形状用 interface,其他场景用 type。团队统一就好,不要纠结。

Q2:Partial<T> 是怎么实现的?

点击查看答案
type Partial<T> = {
  [P in keyof T]?: T[P];
};
  • keyof T:取出 T 的所有属性名,形成一个联合类型
  • [P in keyof T]:遍历每个属性名 P(映射类型 Mapped Types)
  • ?::把每个属性变成可选的
  • T[P]:保持原来的属性类型不变

效果:把 T 的所有属性变成可选的。

Q3:下面的泛型函数有什么问题?怎么修?

function getProperty(obj, key) {
  return obj[key];
}
点击查看答案

问题:没有类型约束,TypeScript 不知道 obj 有哪些属性,也不知道返回值类型是什么。

修复:用泛型 + keyof 约束:

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
  • T:对象的类型
  • K extends keyof T:key 必须是 T 的属性名之一
  • T[K]:返回值类型就是该属性的类型

这样既有类型安全(key 不能乱传),又有精确的返回值类型推断。


一、类型系统基础

1.1 interface vs type

这是面试中出场率最高的 TypeScript 问题之一。

// interface:描述对象的形状
interface User {
  name: string;
  age: number;
}

// type:类型别名,可以是任何类型
type UserID = string | number;
type User = {
  name: string;
  age: number;
};

核心区别

特性interfacetype
对象形状描述支持支持
联合类型不支持type A = B | C
交叉类型用 extendstype A = B & C
声明合并支持不支持
元组不推荐type Pair = [string, number]
条件类型不支持type A = B extends C ? D : E
类实现class Foo implements Barclass Foo implements Bar(type 也行)

声明合并

interface 的声明合并是一个重要特性——同名的 interface 会自动合并属性:

interface Window {
  myCustomProp: string;
}

// 现在 window.myCustomProp 就有类型提示了
// 因为它和 TypeScript 内置的 Window interface 合并了

这在给第三方库扩展类型时非常有用。type 做不到这一点。

实践建议

  • 描述对象/类的形状:优先用 interface
  • 联合类型、交叉类型、条件类型等:只能用 type
  • 团队统一最重要:选一个风格坚持就好

1.2 泛型(Generics)

泛型是 TypeScript 最强大也最让人困惑的特性之一。一句话概括:泛型就是类型的”参数”。

就像函数有参数可以处理不同的值,泛型让类型可以处理不同的类型。

// 没有泛型:只能处理 number 数组
function firstNumber(arr: number[]): number | undefined {
  return arr[0];
}

// 有泛型:可以处理任何类型的数组
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

first([1, 2, 3]);        // 返回类型自动推断为 number | undefined
first(['a', 'b', 'c']);   // 返回类型自动推断为 string | undefined

泛型约束

有时候你需要限制泛型的范围:

// T 必须有 length 属性
function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

getLength('hello');     // OK,string 有 length
getLength([1, 2, 3]);   // OK,array 有 length
getLength(42);          // Error,number 没有 length

常见的泛型应用

// 1. 泛型接口
interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

const userResponse: ApiResponse<User> = { code: 0, message: 'ok', data: { name: 'Alice', age: 25 } };

// 2. 泛型类
class Stack<T> {
  private items: T[] = [];
  push(item: T) { this.items.push(item); }
  pop(): T | undefined { return this.items.pop(); }
}

// 3. 泛型工具函数
function merge<T, U>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

1.3 联合类型与交叉类型

联合类型(Union):A 或 B

type Status = 'loading' | 'success' | 'error';
type ID = string | number;

function printId(id: ID) {
  if (typeof id === 'string') {
    console.log(id.toUpperCase()); // TypeScript 知道这里 id 是 string
  } else {
    console.log(id.toFixed(2));    // TypeScript 知道这里 id 是 number
  }
}

联合类型配合 类型收窄(Type Narrowing) 使用——通过 typeofinstanceofin 等判断,TypeScript 可以在不同的分支中推断出更精确的类型。

交叉类型(Intersection):A 且 B

type HasName = { name: string };
type HasAge = { age: number };
type Person = HasName & HasAge;

// Person 同时拥有 name 和 age
const p: Person = { name: 'Alice', age: 25 };

交叉类型常用于”组合多个类型”,比如 mixin 模式或给已有类型添加新属性。

1.4 字面量类型与类型收窄

// 字面量类型
type Direction = 'up' | 'down' | 'left' | 'right';

function move(direction: Direction) {
  // direction 只能是这四个值之一
}

move('up');     // OK
move('north');  // Error

可辨识联合(Discriminated Union)

这是 TypeScript 中非常实用的模式:

type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'square'; side: number }
  | { kind: 'rectangle'; width: number; height: number };

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;   // TypeScript 知道有 radius
    case 'square':
      return shape.side ** 2;               // TypeScript 知道有 side
    case 'rectangle':
      return shape.width * shape.height;    // TypeScript 知道有 width 和 height
  }
}

kind 属性就是”可辨识标签”,TypeScript 通过它在每个 case 中自动收窄类型。


二、工具类型(Utility Types)

TypeScript 内置了一系列工具类型,它们都是用泛型和映射类型实现的。面试中经常会问到”说几个你常用的工具类型”和”它们是怎么实现的”。

2.1 常用工具类型一览

工具类型作用例子
Partial<T>所有属性变可选Partial<User>
Required<T>所有属性变必选Required<PartialUser>
Readonly<T>所有属性变只读Readonly<User>
Pick<T, K>从 T 中选取部分属性Pick<User, 'name' | 'age'>
Omit<T, K>从 T 中排除部分属性Omit<User, 'password'>
Record<K, V>构造键值类型Record<string, number>
Exclude<T, U>从联合类型中排除Exclude<'a' | 'b' | 'c', 'a'>'b' | 'c'
Extract<T, U>从联合类型中提取Extract<'a' | 'b' | 'c', 'a' | 'b'>'a' | 'b'
NonNullable<T>排除 null 和 undefinedNonNullable<string | null>string
ReturnType<T>获取函数返回值类型ReturnType<typeof fn>
Parameters<T>获取函数参数类型Parameters<typeof fn>

2.2 实现原理

理解工具类型的实现,能帮你更深入地理解 TypeScript 的类型系统。

// Partial:所有属性变可选
type Partial<T> = {
  [P in keyof T]?: T[P];
};

// Required:所有属性变必选(-? 去掉可选标记)
type Required<T> = {
  [P in keyof T]-?: T[P];
};

// Readonly:所有属性变只读
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

// Pick:选取部分属性
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

// Omit:排除部分属性(用 Pick + Exclude 实现)
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

// Record:构造键值类型
type Record<K extends keyof any, T> = {
  [P in K]: T;
};

// Exclude:从联合类型中排除(条件类型 + 分布式特性)
type Exclude<T, U> = T extends U ? never : T;

// ReturnType:获取函数返回值类型
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

2.3 实战应用

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  createdAt: Date;
}

// 创建用户时不需要 id 和 createdAt
type CreateUserInput = Omit<User, 'id' | 'createdAt'>;

// 更新用户时所有字段都是可选的
type UpdateUserInput = Partial<Omit<User, 'id'>>;

// API 响应中不包含 password
type UserResponse = Omit<User, 'password'>;

// 用户列表的索引
type UserMap = Record<number, User>;

三、类型体操入门

“类型体操”是指用 TypeScript 的类型系统做复杂的类型推导和计算。面试中偶尔会出中等难度的类型体操题。

3.1 条件类型

type IsString<T> = T extends string ? true : false;

type A = IsString<'hello'>;  // true
type B = IsString<42>;       // false

3.2 infer 关键字

infer 让你可以在条件类型中”提取”某个位置的类型:

// 提取数组元素类型
type ElementType<T> = T extends (infer E)[] ? E : never;

type A = ElementType<string[]>;    // string
type B = ElementType<number[]>;    // number

// 提取 Promise 内部类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type A = UnwrapPromise<Promise<string>>;  // string
type B = UnwrapPromise<number>;           // number

3.3 模板字面量类型

TypeScript 4.1 引入的强大特性:

type EventName = `on${Capitalize<'click' | 'focus' | 'blur'>}`;
// 结果:'onClick' | 'onFocus' | 'onBlur'

// 实际应用:给事件名加前缀
type PropEventSource<T> = {
  on<K extends string & keyof T>(
    eventName: `${K}Changed`,
    callback: (newValue: T[K]) => void
  ): void;
};

3.4 递归类型

// 深层 Readonly
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

// 深层 Partial
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

四、tsconfig 关键配置

tsconfig.json 有上百个配置项,但面试中常问的只有十几个。

4.1 最关键的配置

{
  "compilerOptions": {
    // 目标 JS 版本
    "target": "ES2020",

    // 模块系统
    "module": "ESNext",
    "moduleResolution": "bundler",

    // 严格模式(强烈建议开启)
    "strict": true,

    // 路径别名
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },

    // 输出
    "outDir": "dist",
    "declaration": true,         // 生成 .d.ts 声明文件
    "declarationMap": true,      // 生成声明文件的 source map
    "sourceMap": true,           // 生成 source map

    // 互操作
    "esModuleInterop": true,     // 允许 import React from 'react'
    "allowSyntheticDefaultImports": true,

    // 类型检查
    "noUnusedLocals": true,      // 未使用的变量报错
    "noUnusedParameters": true,  // 未使用的参数报错
    "noImplicitReturns": true,   // 不允许隐式返回 undefined

    // JSX
    "jsx": "react-jsx",          // React 17+ 的 JSX 转换

    // 跳过 node_modules 的类型检查(提升编译速度)
    "skipLibCheck": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

4.2 strict 模式包含哪些检查?

"strict": true 是一个快捷方式,它会同时开启以下所有严格检查:

选项作用
strictNullChecksnullundefined 不能赋值给其他类型
strictFunctionTypes函数参数类型双向协变检查
strictBindCallApplybind/call/apply 的参数类型检查
noImplicitAny不允许隐式 any 类型
noImplicitThis不允许 this 的类型为隐式 any
alwaysStrict每个文件都添加 "use strict"
strictPropertyInitializationclass 属性必须在构造函数中初始化
useUnknownInCatchVariablescatch 的 error 类型为 unknown 而非 any

强烈建议新项目一开始就开启 strict: true 存量项目可以逐步开启。

4.3 moduleResolution 选项

这是一个容易被忽略但很重要的配置:

含义
node传统的 Node.js 解析规则(找 index.js、读 package.jsonmain
node16 / nodenextNode.js 16+ 的 ESM 解析规则
bundler为打包器优化的解析规则(Vite、Webpack 等),推荐现代项目使用

五、TypeScript 与 ESLint 的集成

5.1 为什么需要 @typescript-eslint?

TypeScript 自带的编译器(tsc)已经能做很多类型检查,但它不做代码质量和风格检查。ESLint 能做代码质量检查,但默认不理解 TypeScript 的语法。

@typescript-eslint 就是把两者连接起来:

  1. @typescript-eslint/parser:让 ESLint 能解析 TypeScript 代码
  2. @typescript-eslint/eslint-plugin:提供 TypeScript 特定的 lint 规则

5.2 配置示例

// eslint.config.js
import tseslint from 'typescript-eslint';

export default tseslint.config(
  ...tseslint.configs.recommended,
  {
    rules: {
      // 用 TS 版本替代 JS 版本
      'no-unused-vars': 'off',
      '@typescript-eslint/no-unused-vars': 'warn',

      // TS 特有规则
      '@typescript-eslint/no-explicit-any': 'warn',
      '@typescript-eslint/no-non-null-assertion': 'warn',
      '@typescript-eslint/consistent-type-imports': 'error',
    }
  }
);

5.3 需要类型信息的规则

@typescript-eslint 有一些规则需要完整的类型信息(不只是 AST),比如:

  • @typescript-eslint/no-floating-promises:检查未处理的 Promise
  • @typescript-eslint/no-misused-promises:检查 Promise 的错误使用

这些规则需要额外配置让 ESLint 可以访问 TypeScript 的类型信息:

export default tseslint.config(
  ...tseslint.configs.recommendedTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        project: true,   // 让 eslint 使用 tsconfig.json
      }
    }
  }
);

注意: 开启类型感知规则会让 ESLint 变慢(因为需要完整的类型检查),在大项目中需要权衡。


六、声明文件(.d.ts)

6.1 什么是声明文件?

声明文件(.d.ts)是 TypeScript 的类型定义文件——它只包含类型声明,不包含实现。它的作用是为 JavaScript 代码提供类型信息。

常见场景:

  • 给 npm 包添加类型@types/react@types/node
  • 给全局变量添加类型:如 window.myGlobalVar
  • 给 JS 模块添加类型:渐进式迁移 TS 时给 JS 文件加类型

6.2 三方包的类型

npm 包的类型来源有三种:

  1. 包自带:包本身用 TypeScript 写的,package.json"types" 字段指向 .d.ts 文件
  2. @types 包:DefinitelyTyped 社区维护的类型定义,安装 @types/xxx 即可
  3. 自己写:如果上面两种都没有,需要自己写声明文件
# 安装 @types 包
npm install -D @types/lodash @types/express

6.3 编写声明文件

给全局变量声明类型

// global.d.ts
declare const __VERSION__: string;
declare const __DEV__: boolean;

// 扩展 Window
interface Window {
  __APP_CONFIG__: {
    apiUrl: string;
    env: string;
  };
}

给没有类型的 JS 模块声明类型

// types/untyped-lib.d.ts
declare module 'untyped-lib' {
  export function doSomething(input: string): number;
  export interface Config {
    verbose: boolean;
    timeout: number;
  }
}

给非 JS 文件声明类型

// types/assets.d.ts
declare module '*.css' {
  const content: Record<string, string>;
  export default content;
}

declare module '*.svg' {
  const content: string;
  export default content;
}

declare module '*.png' {
  const src: string;
  export default src;
}

6.4 发布库时的类型

如果你在开发一个 npm 库,需要生成和发布类型声明:

// tsconfig.json
{
  "compilerOptions": {
    "declaration": true,
    "declarationDir": "dist/types",
    "emitDeclarationOnly": true   // 只生成 .d.ts,不生成 .js(构建交给 Rollup/Vite)
  }
}
// package.json
{
  "types": "dist/types/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/types/index.d.ts",
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js"
    }
  }
}

常见误区

误区一:“TypeScript 有运行时类型检查”

TypeScript 的类型系统是纯编译时的——所有类型信息在编译后都会被擦除,运行时的 JavaScript 代码里没有任何类型信息。所以像 typeof 在运行时只能区分 JS 原生类型(string、number 等),不能区分 TypeScript 的 interface 或 type。如果你需要运行时类型校验(比如验证 API 返回值),需要用 Zod、io-ts 等库。

误区二:“any 和 unknown 差不多”

差别很大。any 是”关闭类型检查”——任何操作都不报错,等于回到了 JavaScript。unknown 是”安全的顶级类型”——你可以把任何值赋给 unknown,但在使用前必须做类型收窄。用 unknown 而不是 any,能保持类型安全。

function handleValue(val: unknown) {
  // val.toUpperCase();  // Error:unknown 不能直接操作
  if (typeof val === 'string') {
    val.toUpperCase();   // OK:收窄后可以操作
  }
}

误区三:“开了 strict 会让代码很难写”

短期来看确实需要写更多的类型注解和做更多的 null 检查。但长期来看,strict 模式能帮你在编译时就发现大量潜在的 bug(尤其是 null/undefined 相关的),减少运行时错误。现在不开 strict,等项目大了再开就更痛苦了。

误区四:“interface 可以被 type 完全替代”

功能上确实有很大重叠,但 interface 有两个 type 做不到的事情:声明合并和报错信息更友好。声明合并在扩展第三方库类型时非常有用,而 TypeScript 在类型不匹配时对 interface 的错误提示通常比 type 更清晰。


小结

本章我们从 TypeScript 的类型系统基础讲起,梳理了 interface vs type、泛型、联合/交叉类型等核心概念,详解了常用工具类型的用法和实现原理,入门了类型体操,并覆盖了 tsconfig 配置、ESLint 集成和声明文件等工程化话题。

核心要点

  1. interface vs type:interface 用于对象形状 + 声明合并,type 用于更复杂的类型表达
  2. 泛型:类型的”参数”,让类型可以复用和泛化
  3. 工具类型Partial / Required / Pick / Omit / Record——理解实现原理(映射类型 + keyof)
  4. 类型体操:条件类型、infer、模板字面量类型、递归类型
  5. tsconfigstrict: true 必开,moduleResolution: 'bundler' 推荐
  6. ESLint 集成@typescript-eslint 提供解析器和 TS 特有规则
  7. 声明文件:为 JS 代码提供类型信息,@types/* 来自 DefinitelyTyped

本章思维导图

TypeScript
  • 类型系统基础
    • interface vs type:声明合并、联合/交叉类型
    • 泛型:类型参数、约束、常见应用
    • 联合类型 / 交叉类型
    • 字面量类型 / 可辨识联合 / 类型收窄
  • 工具类型
    • Partial / Required / Readonly
    • Pick / Omit / Record
    • Exclude / Extract / NonNullable
    • ReturnType / Parameters
    • 实现原理:映射类型 + keyof + 条件类型
  • 类型体操
    • 条件类型:T extends U ? X : Y
    • infer 关键字:提取类型
    • 模板字面量类型
    • 递归类型:DeepReadonly / DeepPartial
  • tsconfig 关键配置
    • strict: true(必开)
    • target / module / moduleResolution
    • paths(路径别名)
    • declaration(生成 .d.ts)
    • skipLibCheck(提升编译速度)
  • ESLint 集成
    • @typescript-eslint/parser + plugin
    • 类型感知规则(需要 project 配置)
  • 声明文件(.d.ts)
    • 来源:包自带 / @types / 自己写
    • 全局声明 / 模块声明 / 非 JS 资源声明
    • 发布库时的类型配置

练习挑战

第一题 ⭐(基础):类型定义

请为以下 API 响应定义 TypeScript 类型:

{
  "code": 200,
  "message": "success",
  "data": {
    "users": [
      { "id": 1, "name": "Alice", "role": "admin" },
      { "id": 2, "name": "Bob", "role": "user" }
    ],
    "total": 2,
    "page": 1
  }
}
点击查看答案与解析
type Role = 'admin' | 'user' | 'guest';

interface User {
  id: number;
  name: string;
  role: Role;
}

interface PaginatedData<T> {
  users: T[];
  total: number;
  page: number;
}

interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

// 使用
type UserListResponse = ApiResponse<PaginatedData<User>>;

要点:

  • 用字面量联合类型定义 Role,而不是 string
  • 用泛型让 ApiResponsePaginatedData 可以复用
  • 分层定义,每个类型职责单一

第二题 ⭐⭐(进阶):实现工具类型

请实现以下两个工具类型:

  1. Mutable<T>:去掉 T 所有属性的 readonly 修饰符(和 Readonly<T> 相反)
  2. Optional<T, K>:只把 T 中指定的 K 属性变成可选的,其他属性保持不变
点击查看答案与解析
// 1. Mutable<T>:去掉所有 readonly
type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

// 验证
interface Config {
  readonly host: string;
  readonly port: number;
}
type MutableConfig = Mutable<Config>;
// { host: string; port: number; }  ← readonly 被去掉了

// 2. Optional<T, K>:只把指定属性变可选
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

// 验证
interface User {
  id: number;
  name: string;
  email: string;
}
type CreateUser = Optional<User, 'id'>;
// { name: string; email: string; id?: number; }  ← 只有 id 变成了可选

解析:

  1. Mutable-readonly 语法去掉 readonly 修饰符,和 Required-? 去掉可选标记是一样的模式
  2. Optional<T, K> 的思路:先用 Omit 去掉 K 属性,再用 Partial<Pick<T, K>> 把 K 属性变成可选的,最后用 & 交叉合并

第三题 ⭐⭐⭐(综合):类型安全的事件系统

请实现一个类型安全的事件发射器的类型定义:

// 要求:
// 1. 事件名和回调参数类型必须匹配
// 2. on 方法注册监听器
// 3. emit 方法触发事件
// 4. off 方法移除监听器

// 使用示例:
interface Events {
  login: { userId: string; timestamp: number };
  logout: { userId: string };
  error: { code: number; message: string };
}

const emitter: EventEmitter<Events> = createEmitter();

emitter.on('login', (data) => {
  console.log(data.userId);     // OK,TypeScript 知道 data 的类型
  console.log(data.timestamp);  // OK
});

emitter.emit('login', { userId: '123', timestamp: Date.now() }); // OK
emitter.emit('login', { userId: '123' });  // Error:缺少 timestamp
emitter.emit('unknown', {});               // Error:不存在的事件名
点击查看答案与解析
interface EventEmitter<T extends Record<string, any>> {
  on<K extends keyof T>(event: K, callback: (data: T[K]) => void): void;
  off<K extends keyof T>(event: K, callback: (data: T[K]) => void): void;
  emit<K extends keyof T>(event: K, data: T[K]): void;
}

function createEmitter<T extends Record<string, any>>(): EventEmitter<T> {
  const listeners = new Map<keyof T, Set<Function>>();

  return {
    on(event, callback) {
      if (!listeners.has(event)) {
        listeners.set(event, new Set());
      }
      listeners.get(event)!.add(callback);
    },
    off(event, callback) {
      listeners.get(event)?.delete(callback);
    },
    emit(event, data) {
      listeners.get(event)?.forEach(cb => cb(data));
    }
  };
}

要点解析:

  • T extends Record<string, any>:约束事件映射的类型
  • K extends keyof T:每个方法中 K 必须是 T 的键(即合法的事件名)
  • T[K]:索引访问类型,获取事件名 K 对应的数据类型
  • on 的回调参数类型 (data: T[K]) => voidemit 的 data 参数类型 T[K] 是同一个类型,保证了类型一致性

这道题考察的是泛型、keyof、索引访问类型的综合应用——这些正是 TypeScript 类型系统中最核心的特性。


自我检测

  • 能说清楚 interfacetype 的核心区别,特别是声明合并
  • 能解释泛型是什么、为什么需要它、常见的使用场景
  • 能列举至少 5 个常用的工具类型并说出它们的作用
  • 能手写 PartialPickOmit 的实现(用映射类型 + keyof)
  • 能解释联合类型和交叉类型的区别,以及类型收窄的常用方式
  • 能说出 tsconfig.jsonstricttargetmodulemoduleResolution 的作用
  • 能配置 @typescript-eslint 让 ESLint 支持 TypeScript
  • 能为无类型的 JS 库编写基本的声明文件(.d.ts

购买课程解锁全部内容

大厂前端面试通关:71 篇构建完整知识体系

¥89.90