Skip to content

递归类型

概述

递归类型(Recursive Types)是 TypeScript 中一种强大的类型定义方式,它允许类型引用自身,从而可以表示具有递归结构的数据类型。递归类型在表示树形结构、链表、嵌套对象等复杂数据结构时非常有用。通过递归类型,我们可以在类型层面精确描述这些递归数据结构,确保类型安全和代码的可维护性。

为什么需要递归类型

在实际开发中,我们经常遇到具有递归结构的数据:

typescript
// 问题:如何表示一个树形结构?
// 树节点可能包含子节点,子节点又是树节点

// 不使用递归类型:无法完整描述结构
type TreeNode = {
  value: number;
  children: TreeNode[];  // ❌ 错误:在定义 TreeNode 时不能引用自身
};

// 使用递归类型:可以完整描述递归结构
type TreeNode = {
  value: number;
  children: TreeNode[];  // ✅ 正确:递归引用自身
};

递归类型让我们能够:

  1. 表示递归数据结构:树、链表、图等具有递归特性的数据结构
  2. 处理嵌套数据:深度嵌套的对象、数组等
  3. 构建递归工具类型:深度处理类型的工具类型
  4. 类型级递归操作:在类型层面实现递归操作

基本语法

类型别名中的递归

在类型别名中,可以直接引用自身:

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 层),超过限制可能导致类型检查失败
  • 性能考虑:复杂的递归类型可能会影响类型检查性能,特别是在大型项目中
  • 函数类型处理:在递归工具类型中,需要特别处理函数类型,通常函数类型不应该被递归处理

重要

  • 递归类型是表示树形、链表等递归数据结构的标准方式
  • 递归工具类型(如 DeepReadonlyDeepPartial)是 TypeScript 类型系统的高级应用
  • 理解递归类型对于处理复杂的数据结构和构建类型工具至关重要
  • 递归类型与条件类型、映射类型结合使用,可以构建强大的类型系统

信息

递归类型的优势

  • 精确类型描述:可以精确描述递归数据结构
  • 类型安全:在编译时确保递归结构的类型正确性
  • 代码可维护性:递归类型使代码更易理解和维护
  • 工具类型构建:可以构建强大的递归工具类型

递归类型的应用场景

  • 树形数据结构(文件系统、菜单、组织架构等)
  • 链表数据结构
  • 嵌套对象和配置
  • JSON 数据处理
  • 递归工具类型(深度处理类型)
  • 语法树和抽象语法树(AST)

相关链接

基于 VitePress 构建