递归类型
概述
递归类型(Recursive Types)是 TypeScript 中一种强大的类型定义方式,它允许类型引用自身,从而可以表示具有递归结构的数据类型。递归类型在表示树形结构、链表、嵌套对象等复杂数据结构时非常有用。通过递归类型,我们可以在类型层面精确描述这些递归数据结构,确保类型安全和代码的可维护性。
为什么需要递归类型
在实际开发中,我们经常遇到具有递归结构的数据:
typescript
// 问题:如何表示一个树形结构?
// 树节点可能包含子节点,子节点又是树节点
// 不使用递归类型:无法完整描述结构
type TreeNode = {
value: number;
children: TreeNode[]; // ❌ 错误:在定义 TreeNode 时不能引用自身
};
// 使用递归类型:可以完整描述递归结构
type TreeNode = {
value: number;
children: TreeNode[]; // ✅ 正确:递归引用自身
};递归类型让我们能够:
- 表示递归数据结构:树、链表、图等具有递归特性的数据结构
- 处理嵌套数据:深度嵌套的对象、数组等
- 构建递归工具类型:深度处理类型的工具类型
- 类型级递归操作:在类型层面实现递归操作
基本语法
类型别名中的递归
在类型别名中,可以直接引用自身:
typescript
// 基本递归类型语法
type RecursiveType = {
// ... 其他属性
self: RecursiveType; // 引用自身
};
// 示例:链表节点
type ListNode<T> = {
value: T;
next: ListNode<T> | null; // 递归引用
};接口中的递归
接口也可以递归引用自身:
typescript
// 接口递归
interface RecursiveInterface {
// ... 其他属性
child: RecursiveInterface; // 递归引用
}
// 示例:文件系统节点
interface FileSystemNode {
name: string;
children: FileSystemNode[]; // 递归引用
}提示
类型别名和接口都支持递归引用,但需要注意避免无限递归导致的类型检查问题。通常使用联合类型(如 | null)来提供递归的终止条件。
链表类型
单向链表
链表是最经典的递归数据结构:
typescript
// 单向链表节点
type ListNode<T> = {
value: T;
next: ListNode<T> | null; // 递归引用,null 作为终止条件
};
// 使用示例
const node1: ListNode<number> = {
value: 1,
next: {
value: 2,
next: {
value: 3,
next: null // 终止条件
}
}
};
// 链表操作函数
function append<T>(list: ListNode<T> | null, value: T): ListNode<T> {
if (list === null) {
return { value, next: null };
}
return {
value: list.value,
next: append(list.next, value)
};
}
// 遍历链表
function traverse<T>(list: ListNode<T> | null, callback: (value: T) => void): void {
if (list === null) return;
callback(list.value);
traverse(list.next, callback);
}双向链表
双向链表包含前向和后向引用:
typescript
// 双向链表节点
type DoublyListNode<T> = {
value: T;
prev: DoublyListNode<T> | null; // 前向引用
next: DoublyListNode<T> | null; // 后向引用
};
// 使用示例
const node: DoublyListNode<string> = {
value: "first",
prev: null,
next: {
value: "second",
prev: null, // 实际使用时需要正确设置
next: null
}
};树形结构类型
二叉树
二叉树是典型的递归数据结构:
typescript
// 二叉树节点
type BinaryTreeNode<T> = {
value: T;
left: BinaryTreeNode<T> | null; // 左子树
right: BinaryTreeNode<T> | null; // 右子树
};
// 使用示例
const tree: BinaryTreeNode<number> = {
value: 1,
left: {
value: 2,
left: null,
right: null
},
right: {
value: 3,
left: {
value: 4,
left: null,
right: null
},
right: null
}
};
// 树遍历函数
function inOrderTraverse<T>(
node: BinaryTreeNode<T> | null,
callback: (value: T) => void
): void {
if (node === null) return;
inOrderTraverse(node.left, callback);
callback(node.value);
inOrderTraverse(node.right, callback);
}多叉树
多叉树节点可以有多个子节点:
typescript
// 多叉树节点
type TreeNode<T> = {
value: T;
children: TreeNode<T>[]; // 子节点数组
};
// 使用示例
const fileTree: TreeNode<string> = {
value: "root",
children: [
{
value: "folder1",
children: [
{ value: "file1.txt", children: [] },
{ value: "file2.txt", children: [] }
]
},
{
value: "folder2",
children: [
{ value: "file3.txt", children: [] }
]
}
]
};
// 深度优先搜索
function dfs<T>(
node: TreeNode<T>,
target: T
): TreeNode<T> | null {
if (node.value === target) {
return node;
}
for (const child of node.children) {
const result = dfs(child, target);
if (result !== null) {
return result;
}
}
return null;
}嵌套对象类型
深度嵌套对象
递归类型可以表示任意深度的嵌套对象:
typescript
// 深度嵌套对象类型
type NestedObject = {
[key: string]: string | number | boolean | NestedObject;
};
// 使用示例
const nested: NestedObject = {
level1: {
level2: {
level3: {
value: "deep value"
},
other: 42
},
name: "test"
},
top: "level"
};
// 深度访问函数
function getNestedValue(
obj: NestedObject,
path: string[]
): string | number | boolean | NestedObject | undefined {
let current: any = obj;
for (const key of path) {
if (current && typeof current === 'object' && !Array.isArray(current)) {
current = current[key];
} else {
return undefined;
}
}
return current;
}JSON 类型
JSON 数据本身就是递归的:
typescript
// JSON 值类型(递归定义)
type JSONValue =
| string
| number
| boolean
| null
| JSONObject
| JSONArray;
type JSONObject = {
[key: string]: JSONValue;
};
type JSONArray = JSONValue[];
// 使用示例
const jsonData: JSONValue = {
name: "John",
age: 30,
active: true,
address: {
city: "Beijing",
zip: "100000"
},
tags: ["developer", "typescript"],
metadata: null
};
// JSON 解析函数
function parseJSON(json: string): JSONValue {
return JSON.parse(json);
}
// 类型安全的 JSON 访问
function getJSONValue(obj: JSONObject, key: string): JSONValue | undefined {
return obj[key];
}递归工具类型
深度只读
将对象的所有层级都变为只读:
typescript
// 深度只读类型
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? T[P] extends Function
? T[P] // 函数类型保持不变
: DeepReadonly<T[P]> // 递归处理对象类型
: T[P]; // 基本类型直接使用
};
// 使用示例
type User = {
name: string;
profile: {
age: number;
address: {
city: string;
country: string;
};
};
};
type ReadonlyUser = DeepReadonly<User>;
// 所有层级的属性都是只读的
// 测试
const user: ReadonlyUser = {
name: "John",
profile: {
age: 30,
address: {
city: "Beijing",
country: "China"
}
}
};
// user.profile.address.city = "Shanghai"; // ❌ 错误:只读属性深度可选
将对象的所有层级都变为可选:
typescript
// 深度可选类型
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? T[P] extends Function
? T[P] // 函数类型保持不变
: DeepPartial<T[P]> // 递归处理对象类型
: T[P]; // 基本类型直接使用
};
// 使用示例
type Config = {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
api: {
baseUrl: string;
timeout: number;
};
};
type PartialConfig = DeepPartial<Config>;
// 所有层级的属性都是可选的
// 使用示例
const partialConfig: PartialConfig = {
database: {
host: "localhost"
// 其他属性都是可选的
}
};深度必需
将对象的所有层级都变为必需:
typescript
// 深度必需类型
type DeepRequired<T> = {
[P in keyof T]-?: T[P] extends object
? T[P] extends Function
? T[P] // 函数类型保持不变
: DeepRequired<T[P]> // 递归处理对象类型
: T[P]; // 基本类型直接使用
};
// 使用示例
type OptionalConfig = {
database?: {
host?: string;
port?: number;
credentials?: {
username?: string;
password?: string;
};
};
api?: {
baseUrl?: string;
timeout?: number;
};
};
type RequiredConfig = DeepRequired<OptionalConfig>;
// 所有层级的属性都是必需的深度提取类型
从嵌套对象中提取特定类型:
typescript
// 深度提取函数类型
type DeepExtractFunction<T> = {
[K in keyof T]: T[K] extends Function
? T[K] // 保留函数类型
: T[K] extends object
? DeepExtractFunction<T[K]> // 递归处理对象
: never; // 排除非函数、非对象类型
};
// 使用示例
type MixedType = {
name: string;
handler: () => void;
config: {
value: number;
processor: (x: number) => number;
nested: {
callback: (data: string) => boolean;
};
};
};
type FunctionsOnly = DeepExtractFunction<MixedType>;
// 类型:{ handler: () => void; config: { processor: (x: number) => number; nested: { callback: (data: string) => boolean } } }实际应用场景
场景 1:文件系统表示
使用递归类型表示文件系统:
typescript
// 文件系统节点
type FileSystemNode = {
name: string;
type: 'file' | 'directory';
children?: FileSystemNode[]; // 目录可以有子节点
size?: number; // 文件大小
content?: string; // 文件内容
};
// 使用示例
const fileSystem: FileSystemNode = {
name: "root",
type: "directory",
children: [
{
name: "src",
type: "directory",
children: [
{
name: "index.ts",
type: "file",
size: 1024,
content: "export const x = 1;"
},
{
name: "utils",
type: "directory",
children: [
{
name: "helper.ts",
type: "file",
size: 512
}
]
}
]
},
{
name: "README.md",
type: "file",
size: 256
}
]
};
// 查找文件
function findFile(
node: FileSystemNode,
fileName: string
): FileSystemNode | null {
if (node.type === 'file' && node.name === fileName) {
return node;
}
if (node.children) {
for (const child of node.children) {
const result = findFile(child, fileName);
if (result !== null) {
return result;
}
}
}
return null;
}场景 2:评论系统
表示嵌套的评论结构:
typescript
// 评论类型
type Comment = {
id: string;
author: string;
content: string;
timestamp: Date;
replies: Comment[]; // 递归引用:评论可以有回复
likes: number;
};
// 使用示例
const comments: Comment[] = [
{
id: "1",
author: "Alice",
content: "Great article!",
timestamp: new Date(),
replies: [
{
id: "2",
author: "Bob",
content: "I agree!",
timestamp: new Date(),
replies: [],
likes: 5
}
],
likes: 10
},
{
id: "3",
author: "Charlie",
content: "Thanks for sharing",
timestamp: new Date(),
replies: [],
likes: 3
}
];
// 计算总评论数(包括回复)
function countComments(comment: Comment): number {
return 1 + comment.replies.reduce(
(sum, reply) => sum + countComments(reply),
0
);
}
// 获取所有评论作者
function getAllAuthors(comment: Comment): string[] {
return [
comment.author,
...comment.replies.flatMap(reply => getAllAuthors(reply))
];
}场景 3:菜单系统
表示多级菜单结构:
typescript
// 菜单项类型
type MenuItem = {
id: string;
label: string;
path?: string;
icon?: string;
children?: MenuItem[]; // 递归引用:菜单可以有子菜单
};
// 使用示例
const menu: MenuItem[] = [
{
id: "1",
label: "首页",
path: "/",
icon: "home"
},
{
id: "2",
label: "产品",
children: [
{
id: "2-1",
label: "产品列表",
path: "/products"
},
{
id: "2-2",
label: "产品分类",
children: [
{
id: "2-2-1",
label: "电子产品",
path: "/products/electronics"
},
{
id: "2-2-2",
label: "服装",
path: "/products/clothing"
}
]
}
]
},
{
id: "3",
label: "关于",
path: "/about"
}
];
// 查找菜单项
function findMenuItem(
items: MenuItem[],
id: string
): MenuItem | null {
for (const item of items) {
if (item.id === id) {
return item;
}
if (item.children) {
const found = findMenuItem(item.children, id);
if (found !== null) {
return found;
}
}
}
return null;
}
// 获取所有菜单路径
function getAllPaths(item: MenuItem): string[] {
const paths: string[] = [];
if (item.path) {
paths.push(item.path);
}
if (item.children) {
for (const child of item.children) {
paths.push(...getAllPaths(child));
}
}
return paths;
}场景 4:配置对象处理
处理深度嵌套的配置对象:
typescript
// 配置类型
type AppConfig = {
app: {
name: string;
version: string;
};
database: {
host: string;
port: number;
connection: {
pool: {
min: number;
max: number;
};
timeout: number;
};
};
features: {
auth: {
enabled: boolean;
providers: string[];
};
cache: {
enabled: boolean;
ttl: number;
};
};
};
// 深度合并配置
type DeepMerge<T, U> = {
[K in keyof T | keyof U]: K extends keyof U
? K extends keyof T
? T[K] extends object
? U[K] extends object
? DeepMerge<T[K], U[K]> // 递归合并对象
: U[K] // U 的值覆盖
: U[K] // 非对象类型直接覆盖
: U[K] // U 中新增的属性
: K extends keyof T
? T[K] // T 中独有的属性
: never;
};
// 使用示例
const defaultConfig: AppConfig = {
app: { name: "MyApp", version: "1.0.0" },
database: {
host: "localhost",
port: 5432,
connection: {
pool: { min: 1, max: 10 },
timeout: 5000
}
},
features: {
auth: { enabled: true, providers: [] },
cache: { enabled: false, ttl: 3600 }
}
};
const userConfig: DeepPartial<AppConfig> = {
database: {
connection: {
pool: { max: 20 } // 只覆盖部分配置
}
}
};
// 类型安全的配置合并
function mergeConfig<T>(defaults: T, overrides: DeepPartial<T>): T {
return { ...defaults, ...overrides } as T;
}高级用法
递归类型约束
在递归类型中使用约束:
typescript
// 带约束的递归类型
type RecursiveArray<T extends { id: string }> = {
id: string;
value: T;
children: RecursiveArray<T>[];
};
// 使用示例
type Category = {
id: string;
name: string;
};
const categories: RecursiveArray<Category> = {
id: "1",
value: { id: "1", name: "Root" },
children: [
{
id: "2",
value: { id: "2", name: "Child 1" },
children: []
}
]
};条件递归
根据条件决定是否递归:
typescript
// 条件递归类型
type ConditionalRecursive<T, Depth extends number = 3> =
Depth extends 0
? T // 达到深度限制,停止递归
: {
value: T;
nested: ConditionalRecursive<T, Prev<Depth>>; // 递归但减少深度
};
// 辅助类型:减少数字(简化版)
type Prev<T extends number> = T extends 3 ? 2 : T extends 2 ? 1 : 0;
// 使用示例
type LimitedNested = ConditionalRecursive<string, 2>;
// 类型:{ value: string; nested: { value: string; nested: string } }互递归类型
两个类型相互引用:
typescript
// 互递归类型
type Expression =
| { type: 'number'; value: number }
| { type: 'variable'; name: string }
| { type: 'binary'; operator: string; left: Expression; right: Expression }
| { type: 'call'; function: Expression; arguments: Expression[] };
type Statement =
| { type: 'expression'; expr: Expression }
| { type: 'assignment'; variable: string; value: Expression }
| { type: 'if'; condition: Expression; then: Statement; else?: Statement }
| { type: 'block'; statements: Statement[] };
// 使用示例
const program: Statement = {
type: 'block',
statements: [
{
type: 'assignment',
variable: 'x',
value: {
type: 'binary',
operator: '+',
left: { type: 'number', value: 1 },
right: { type: 'number', value: 2 }
}
},
{
type: 'if',
condition: {
type: 'binary',
operator: '>',
left: { type: 'variable', name: 'x' },
right: { type: 'number', value: 0 }
},
then: {
type: 'expression',
expr: { type: 'variable', name: 'x' }
}
}
]
};类型检查示例
常见错误
typescript
// ❌ 错误:缺少终止条件,导致无限递归
type InfiniteRecursive = {
value: string;
next: InfiniteRecursive; // 没有 null 或 undefined,无法终止
};
// ❌ 错误:直接循环引用(在定义时)
type A = {
b: B; // 错误:B 还未定义
};
type B = {
a: A;
};
// ✅ 正确:使用联合类型提供终止条件
type SafeRecursive = {
value: string;
next: SafeRecursive | null; // null 作为终止条件
};
// ✅ 正确:先定义基础类型,再建立循环引用
type A = {
b: B | null;
};
type B = {
a: A | null;
};正确写法
typescript
// ✅ 正确:链表类型
type ListNode<T> = {
value: T;
next: ListNode<T> | null;
};
// ✅ 正确:树类型
type TreeNode<T> = {
value: T;
children: TreeNode<T>[];
};
// ✅ 正确:递归工具类型
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? T[P] extends Function
? T[P]
: DeepReadonly<T[P]>
: T[P];
};
// ✅ 正确:带约束的递归类型
type RecursiveWithConstraint<T extends { id: string }> = {
data: T;
children: RecursiveWithConstraint<T>[];
};注意事项
提示
- 递归类型必须提供终止条件(如
| null、| undefined或空数组[]),否则会导致类型检查问题 - 递归类型在类型检查时可能会有性能影响,特别是深度很大的递归
- 使用递归工具类型时,要注意函数类型的处理,避免将函数类型也递归处理
注意
- 无限递归风险:如果递归类型没有终止条件,TypeScript 编译器可能会报错或导致类型检查变慢
- 深度限制:TypeScript 对递归深度有限制(通常约 50 层),超过限制可能导致类型检查失败
- 性能考虑:复杂的递归类型可能会影响类型检查性能,特别是在大型项目中
- 函数类型处理:在递归工具类型中,需要特别处理函数类型,通常函数类型不应该被递归处理
重要
- 递归类型是表示树形、链表等递归数据结构的标准方式
- 递归工具类型(如
DeepReadonly、DeepPartial)是 TypeScript 类型系统的高级应用 - 理解递归类型对于处理复杂的数据结构和构建类型工具至关重要
- 递归类型与条件类型、映射类型结合使用,可以构建强大的类型系统
信息
递归类型的优势:
- 精确类型描述:可以精确描述递归数据结构
- 类型安全:在编译时确保递归结构的类型正确性
- 代码可维护性:递归类型使代码更易理解和维护
- 工具类型构建:可以构建强大的递归工具类型
递归类型的应用场景:
- 树形数据结构(文件系统、菜单、组织架构等)
- 链表数据结构
- 嵌套对象和配置
- JSON 数据处理
- 递归工具类型(深度处理类型)
- 语法树和抽象语法树(AST)