工程化篇 | TypeScript
前言
TypeScript 已经从”可选项”变成了前端项目的”标配”。据 2024 年 State of JS 调查,超过 80% 的开发者在日常工作中使用 TypeScript。Vue 3 用 TypeScript 重写了,Angular 从一开始就基于 TypeScript,React 社区也早已全面拥抱 TypeScript。
但很多人对 TypeScript 的理解还停留在”给变量加个类型注解”的阶段。面试中被问到 TypeScript,常见的卡壳场景有:
interface和type到底该用哪个?有什么区别?- 泛型是什么?什么场景下需要用泛型?
Partial、Pick、Omit、Record这些工具类型是怎么实现的?tsconfig.json里那么多选项,哪些是关键的?- 声明文件(
.d.ts)是做什么的?怎么写?
本章,我们从 TypeScript 类型系统的核心概念讲起,梳理工具类型、类型体操入门、tsconfig 关键配置、与 ESLint 的集成,最后聊聊声明文件。
诊断自测
Q1:interface 和 type 有什么核心区别?什么时候用哪个?
点击查看答案
核心区别:
- interface 支持声明合并(Declaration Merging),type 不支持——同名 interface 会自动合并,同名 type 会报错
- type 支持更多类型表达——联合类型、交叉类型、元组类型、条件类型等只能用 type 定义
- 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;
};
核心区别
| 特性 | interface | type |
|---|---|---|
| 对象形状描述 | 支持 | 支持 |
| 联合类型 | 不支持 | type A = B | C |
| 交叉类型 | 用 extends | type A = B & C |
| 声明合并 | 支持 | 不支持 |
| 元组 | 不推荐 | type Pair = [string, number] |
| 条件类型 | 不支持 | type A = B extends C ? D : E |
| 类实现 | class Foo implements Bar | class 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) 使用——通过 typeof、instanceof、in 等判断,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 和 undefined | NonNullable<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 是一个快捷方式,它会同时开启以下所有严格检查:
| 选项 | 作用 |
|---|---|
strictNullChecks | null 和 undefined 不能赋值给其他类型 |
strictFunctionTypes | 函数参数类型双向协变检查 |
strictBindCallApply | bind/call/apply 的参数类型检查 |
noImplicitAny | 不允许隐式 any 类型 |
noImplicitThis | 不允许 this 的类型为隐式 any |
alwaysStrict | 每个文件都添加 "use strict" |
strictPropertyInitialization | class 属性必须在构造函数中初始化 |
useUnknownInCatchVariables | catch 的 error 类型为 unknown 而非 any |
强烈建议新项目一开始就开启 strict: true。 存量项目可以逐步开启。
4.3 moduleResolution 选项
这是一个容易被忽略但很重要的配置:
| 值 | 含义 |
|---|---|
node | 传统的 Node.js 解析规则(找 index.js、读 package.json 的 main) |
node16 / nodenext | Node.js 16+ 的 ESM 解析规则 |
bundler | 为打包器优化的解析规则(Vite、Webpack 等),推荐现代项目使用 |
五、TypeScript 与 ESLint 的集成
5.1 为什么需要 @typescript-eslint?
TypeScript 自带的编译器(tsc)已经能做很多类型检查,但它不做代码质量和风格检查。ESLint 能做代码质量检查,但默认不理解 TypeScript 的语法。
@typescript-eslint 就是把两者连接起来:
@typescript-eslint/parser:让 ESLint 能解析 TypeScript 代码@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 包的类型来源有三种:
- 包自带:包本身用 TypeScript 写的,
package.json中"types"字段指向.d.ts文件 - @types 包:DefinitelyTyped 社区维护的类型定义,安装
@types/xxx即可 - 自己写:如果上面两种都没有,需要自己写声明文件
# 安装 @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 集成和声明文件等工程化话题。
核心要点
- interface vs type:interface 用于对象形状 + 声明合并,type 用于更复杂的类型表达
- 泛型:类型的”参数”,让类型可以复用和泛化
- 工具类型:
Partial/Required/Pick/Omit/Record——理解实现原理(映射类型 + keyof) - 类型体操:条件类型、infer、模板字面量类型、递归类型
- tsconfig:
strict: true必开,moduleResolution: 'bundler'推荐 - ESLint 集成:
@typescript-eslint提供解析器和 TS 特有规则 - 声明文件:为 JS 代码提供类型信息,
@types/*来自 DefinitelyTyped
本章思维导图
- 类型系统基础
- 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 - 用泛型让
ApiResponse和PaginatedData可以复用 - 分层定义,每个类型职责单一
第二题 ⭐⭐(进阶):实现工具类型
请实现以下两个工具类型:
Mutable<T>:去掉 T 所有属性的readonly修饰符(和Readonly<T>相反)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 变成了可选
解析:
Mutable用-readonly语法去掉 readonly 修饰符,和Required用-?去掉可选标记是一样的模式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]) => void和emit的 data 参数类型T[K]是同一个类型,保证了类型一致性
这道题考察的是泛型、keyof、索引访问类型的综合应用——这些正是 TypeScript 类型系统中最核心的特性。
自我检测
- 能说清楚
interface和type的核心区别,特别是声明合并 - 能解释泛型是什么、为什么需要它、常见的使用场景
- 能列举至少 5 个常用的工具类型并说出它们的作用
- 能手写
Partial、Pick、Omit的实现(用映射类型 + keyof) - 能解释联合类型和交叉类型的区别,以及类型收窄的常用方式
- 能说出
tsconfig.json中strict、target、module、moduleResolution的作用 - 能配置
@typescript-eslint让 ESLint 支持 TypeScript - 能为无类型的 JS 库编写基本的声明文件(
.d.ts)
购买课程解锁全部内容
大厂前端面试通关:71 篇构建完整知识体系
¥89.90