第 1 节 概述
前几篇我们学会了 TypeScript 的日常用法:基础类型、接口、函数、类、泛型、模块。
但 TypeScript 的类型系统远比这强大——它实际上是一门独立的编程语言。你可以对类型做运算、条件判断、循环遍历、字符串拼接……
本篇的内容偏"进阶"。理解它们需要一段时间,但一旦掌握,你就能写出更灵活、更安全的类型定义。
第 2 节 映射类型
映射类型可以基于一个类型,生成另一个类型。它的语法像循环:
1
2
| type T = { [K in "a" | "b" | "c"]: boolean }
// 等价于 { a: boolean; b: boolean; c: boolean }
|
真实场景——把一个接口的所有属性变为可选:
1
2
3
4
5
6
7
8
9
10
11
12
| interface Person {
name: string
age: number
email: string
}
// 手动写一份可选版本
type PartialPerson = {
name?: string
age?: number
email?: string
}
|
用映射类型自动生成:
1
2
3
4
5
6
| type MyPartial<T> = {
[K in keyof T]?: T[K]
}
type PartialPerson = MyPartial<Person>
// { name?: string; age?: number; email?: string }
|
在映射类型中可以加修饰符:? 表示可选,readonly 表示只读。前缀 - 表示去除某个修饰符:
1
2
3
4
5
6
7
8
9
| // 去除所有 readonly
type Mutable<T> = {
-readonly [K in keyof T]: T[K]
}
// 去除所有可选标记
type Required<T> = {
[K in keyof T]-?: T[K]
}
|
第 3 节 条件类型
条件类型为类型添加了"if-else"逻辑:
1
2
3
4
| type IsString<T> = T extends string ? "是字符串" : "不是字符串"
type A = IsString<"hello"> // "是字符串"
type B = IsString<42> // "不是字符串"
|
理解
条件类型类似于三元运算符:T extends U ? X : Y。如果 T 能赋值给 U,结果为 X,否则为 Y。
实际用途——根据参数类型决定返回值类型:
1
2
3
4
5
6
| type ApiResponse<T> = T extends "user" ? { id: number; name: string }
: T extends "post" ? { id: number; title: string }
: { error: string }
type UserRes = ApiResponse<"user"> // { id: number; name: string }
type PostRes = ApiResponse<"post"> // { id: number; title: string }
|
3.1 条件类型中的 infer
infer 可以在条件类型中声明一个"待推断"的类型变量:
1
2
3
4
5
6
7
8
9
10
11
12
| // 获取数组元素的类型
type ElementType<T> = T extends (infer E)[] ? E : never
type A = ElementType<string[]> // string
type B = ElementType<number[]> // number
type C = ElementType<boolean> // never(不是数组)
// 获取函数返回值的类型
type ReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never
type Fn = (x: number) => string
type R = ReturnType<Fn> // string
|
infer 是高级类型编程中最强大的工具之一。它让 TypeScript 帮我们"反向推导"出类型。
第 4 节 模板字面量类型
TypeScript 4.1 引入了模板字面量类型——就像 JavaScript 的模板字符串,但在类型层面:
1
2
3
4
5
6
7
8
9
10
| type EventName = `on${Capitalize<string>}`
// 组合字符串字面量
type Size = "small" | "medium" | "large"
type Color = "red" | "green" | "blue"
type ButtonVariant = `${Size}-${Color}`
// "small-red" | "small-green" | "small-blue"
// | "medium-red" | "medium-green" | "medium-blue"
// | "large-red" | "large-green" | "large-blue"
|
实用的例子——约束事件处理函数的命名:
1
2
3
4
| type EventHandler<K extends string> = `on${Capitalize<K>}`
type ClickHandler = EventHandler<"click"> // "onClick"
type ChangeHandler = EventHandler<"change"> // "onChange"
|
第 5 节 内置工具类型
TypeScript 内置了一套常用的泛型工具类型,可以让你的类型编程事半功倍:
5.1 对象操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| interface User {
name: string
age: number
email: string
password: string
}
// Partial<T> —— 所有属性变为可选
type PartialUser = Partial<User>
// { name?: string; age?: number; email?: string; password?: string }
// Required<T> —— 所有属性变为必选
type RequiredUser = Required<PartialUser>
// { name: string; age: number; email: string; password: string }
// Readonly<T> —— 所有属性变为只读
type ReadonlyUser = Readonly<User>
// Pick<T, K> —— 挑选部分属性
type PublicUser = Pick<User, "name" | "email">
// { name: string; email: string }
// Omit<T, K> —— 排除部分属性
type InternalUser = Omit<User, "password">
// { name: string; age: number; email: string }
|
5.2 联合类型操作
1
2
3
4
5
6
7
8
9
10
11
12
13
| type Status = "idle" | "loading" | "success" | "error"
// Exclude<T, U> —— 排除某些类型
type NoLoading = Exclude<Status, "loading">
// "idle" | "success" | "error"
// Extract<T, U> —— 提取某些类型
type LoadingStates = Extract<Status, "loading" | "error">
// "loading" | "error"
// NonNullable<T> —— 去除 null 和 undefined
type Maybe = string | null | undefined
type Sure = NonNullable<Maybe> // string
|
5.3 记录类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // Record<K, V> —— 构造键值对类型
type PageRoutes = Record<string, string>
const routes: PageRoutes = {
home: "/",
about: "/about",
}
// 更精确的用法——限制键
type PageName = "home" | "about" | "contact"
type PageInfo = Record<PageName, { title: string; path: string }>
const pages: PageInfo = {
home: { title: "首页", path: "/" },
about: { title: "关于", path: "/about" },
contact: { title: "联系", path: "/contact" },
}
|
5.4 其他常用工具
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // Awaited<T> —— 获取 Promise 内部类型
type AsyncResult = Awaited<Promise<string>> // string
type DeepAsync = Awaited<Promise<Promise<number>>> // number
// Parameters<T> —— 获取函数参数类型(元组)
type Fn = (name: string, age: number) => void
type FnParams = Parameters<Fn> // [string, number]
// ConstructorParameters<T> —— 获取构造参数类型
class Person { constructor(name: string, age: number) {} }
type PersonParams = ConstructorParameters<typeof Person> // [string, number]
// ReturnType<T> —— 获取函数返回值类型
type FnReturn = ReturnType<() => string> // string
// InstanceType<T> —— 获取类实例的类型
type PersonInstance = InstanceType<typeof Person> // Person
|
第 6 节 声明文件(.d.ts)
当你在 TypeScript 中使用 JavaScript 库(如 lodash、jQuery)时,需要类型声明文件来告诉 TypeScript 这些函数的类型。
1
2
3
| // lodash 的简化声明
declare function chunk<T>(array: T[], size?: number): T[][]
declare function shuffle<T>(array: T[]): T[]
|
6.1 声明文件的来源
- 自带声明:库的
package.json 中有 "types": "index.d.ts",如 Vue - DefinitelyTyped:
npm install @types/lodash,社区维护的声明 - 自己写:在项目中创建
.d.ts 文件
6.2 declare 关键字
declare 告诉 TypeScript"这个变量存在,但实现在别处":
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // 声明全局变量
declare const API_VERSION: string
// 声明全局函数
declare function fetchData(url: string): Promise<unknown>
// 声明模块
declare module "my-lib" {
export function doSomething(): void
export const VERSION: string
}
// 声明类
declare class MyClass {
constructor(name: string)
greet(): string
}
|
6.3 环境配置
在 .d.ts 文件中声明全局类型,整个项目都可以使用:
1
2
3
4
5
6
7
8
9
10
| // env.d.ts —— 放在项目 src 目录下
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: "development" | "production" | "test"
API_URL: string
}
}
// 使用
const env = process.env.NODE_ENV // 类型为 "development" | "production" | "test"
|
注意
声明文件(.d.ts)只包含类型信息,不包含实现。它就像一份"说明书",告诉 TypeScript 外部世界的类型。编译时不会生成对应的 JS 代码。
进阶之路到这里就告一段落了。你接触了 TypeScript 类型系统的"编程"能力——映射、条件、推断、模板字面量、工具类型。
好的,理论足够多了。下一篇是真正的收尾——用 TypeScript 从头到尾做一个完整项目。