现在位置: 首页 > TypeScript 教程 > 正文

TypeScript 递归类型

递归类型是一种引用自身的类型,在处理树结构、嵌套数据时非常有用。

TypeScript 支持递归类型定义,可以表达无限深度的数据结构。


递归类型工作原理 基础节点 interface TreeNode { value: string } 递归引用 递归类型 interface TreeNode { value: string children?: TreeNode[] } 实际结构 root ├── child1 └── child2 递归类型应用场景 树形结构 嵌套对象 深度类型转换

为什么需要递归类型

在现实世界中,数据结构往往是嵌套的。

例如,文件系统有文件夹和子文件夹,组织架构有部门和子部门,JSON 数据可以无限嵌套。

递归类型允许我们表达这种无限嵌套的结构,是处理树形数据的基石。

概念:递归类型是指在类型定义中引用自身的类型,可以表达任意深度的嵌套结构。


树形结构

递归类型最常见的应用是表示树形结构。

实例

// 定义树节点类型,children 引用自身
interface TreeNode {
    id: number;                    // 节点ID
    name: string;                  // 节点名称
    children?: TreeNode[];         // 子节点数组,递归引用
}

// 创建树形结构
const fileSystem: TreeNode = {
    id: 1,
    name: "根目录",
    children: [
        {
            id: 2,
            name: "文件夹1",
            children: [
                { id: 5, name: "文件A.txt" },
                { id: 6, name: "文件B.txt" }
            ]
        },
        {
            id: 3,
            name: "文件夹2",
            children: [
                { id: 7, name: "文件C.txt" }
            ]
        },
        {
            id: 4,
            name: "文件.txt"
        }
    ]
};

// 遍历树的函数
function traverse(node: TreeNode, depth: number = 0): void {
    const indent = "  ".repeat(depth);
    console.log(indent + "📁 " + node.name);

    if (node.children) {
        for (const child of node.children) {
            traverse(child, depth + 1);
        }
    }
}

traverse(fileSystem);

运行结果:

📁 根目录
  📁 文件夹1
    📁 文件A.txt
    📁 文件B.txt
  📁 文件夹2
    📁 文件C.txt
  📁 文件.txt

文件系统:树形结构是递归类型的经典应用,可以表示目录树、组织结构等。


嵌套列表

递归类型也可以表示嵌套的列表结构。

实例

// 定义嵌套列表类型
type NestedList<T> = T | NestedList<T>[];

// 定义任务类型
interface Task {
    id: number;
    title: string;
    completed: boolean;
}

// 创建嵌套任务列表
const tasks: NestedList<Task> = [
    { id: 1, title: "项目A", completed: false },
    [
        { id: 2, title: "子任务1", completed: true },
        { id: 3, title: "子任务2", completed: false }
    ],
    { id: 4, title: "项目B", completed: false }
];

// 计算嵌套列表深度
function getDepth<T>(list: NestedList<T>, depth: number = 0): number {
    if (Array.isArray(list)) {
        let maxDepth = depth + 1;
        for (const item of list) {
            maxDepth = Math.max(maxDepth, getDepth(item, depth + 1));
        }
        return maxDepth;
    }
    return depth;
}

console.log("列表深度: " + getDepth(tasks));

联合类型:使用 T | NestedList[] 可以同时处理单个元素和数组。


深度只读类型

使用递归类型实现深度只读转换。

实例

// 深度只读类型 - 递归应用
type DeepReadonly<T> = T extends Function
    ? T  // 函数保持原样
    : T extends object
        ? { readonly [P in keyof T]: DeepReadonly<T[P]> }
        : T;

// 用户类型
interface User {
    name: string;
    profile: {
        email: string;
        address: {
            city: string;
            zip: string;
        };
    };
    friends: User[];
}

// 创建深度只读用户
const user: DeepReadonly<User> = {
    name: "Alice",
    profile: {
        email: "alice@test.com",
        address: {
            city: "Beijing",
            zip: "100000"
        }
    },
    friends: []
};

// 尝试修改会报错
// user.name = "Bob"; // 错误:name 是只读的
// user.profile.address.city = "Shanghai"; // 错误:深层也是只读的

console.log("用户: " + user.name);
console.log("城市: " + user.profile.address.city);

递归转换:DeepReadonly 会递归地将所有嵌套对象属性转换为只读。


深度可选类型

使用递归类型实现深度可选转换。

实例

// 深度可选类型 - 递归应用
type DeepPartial<T> = T extends object
    ? { [P in keyof T]?: DeepPartial<T[P]> }
    : T;

// 配置类型
interface AppConfig {
    database: {
        host: string;
        port: number;
        credentials: {
            username: string;
            password: string;
        };
    };
    server: {
        port: number;
        ssl: boolean;
    };
}

// 使用深度可选,可以只提供部分配置
const partialConfig: DeepPartial<AppConfig> = {
    database: {
        host: "localhost"
        // port 和 credentials 可选
    }
    // server 可选
};

console.log("数据库主机: " + partialConfig.database?.host);

可选嵌套:DeepPartial 递归地将所有属性变为可选,便于处理部分配置。


链式数据结构

递归类型可以表示链表等链式数据结构。

实例

// 链表节点类型
interface ListNode<T> {
    value: T;              // 当前节点的值
    next?: ListNode<T>;    // 下一个节点,递归引用
}

// 创建链表
const linkedList: ListNode<number> = {
    value: 1,
    next: {
        value: 2,
        next: {
            value: 3,
            next: {
                value: 4,
                next: undefined
            }
        }
    }
};

// 遍历链表
function traverseList<T>(node: ListNode<T>): void {
    let current: ListNode<T> | undefined = node;
    const values: T[] = [];

    while (current) {
        values.push(current.value);
        current = current.next;
    }

    console.log("链表值: " + values.join(" -> "));
}

traverseList(linkedList);

// 计算链表长度
function getLength<T>(node: ListNode<T>): number {
    let length = 0;
    let current: ListNode<T> | undefined = node;

    while (current) {
        length++;
        current = current.next;
    }

    return length;
}

console.log("链表长度: " + getLength(linkedList));

链表:ListNode 通过 next 引用自身,形成链式结构,是递归类型的经典应用。


联合类型的递归

使用递归类型处理 JSON 数据的联合类型。

JSON 类型:递归类型可以精确表达 JSON 的所有可能类型。

实例

// JSON 值的递归类型定义
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };

// 定义配置对象
const config: JSONValue = {
    "name": "my-app",
    "version": "1.0.0",
    "enabled": true,
    "settings": {
        "debug": false,
        "ports": [3000, 8080],
        "metadata": {
            "author": "Alice",
            "tags": ["web", "typescript"]
        }
    }
};

// 获取 JSON 值的函数
function getValue(obj: JSONValue, path: string): JSONValue | undefined {
    const keys = path.split(".");
    let current: JSONValue | undefined = obj;

    for (const key of keys) {
        if (current && typeof current === "object" && !Array.isArray(current)) {
            current = (current as { [key: string]: JSONValue })[key];
        } else {
            return undefined;
        }
    }

    return current;
}

console.log("版本: " + getValue(config, "version"));
console.log("端口: " + getValue(config, "settings.ports"));
console.log("作者: " + getValue(config, "settings.metadata.author"));

注意事项

  • 递归基例:确保递归类型有终止条件,避免无限递归
  • 条件类型:递归通常与条件类型结合使用
  • 深度限制:TypeScript 编译器对递归深度有限制
  • 性能考虑:深度递归可能影响类型检查性能

最佳实践:递归类型是处理树形和嵌套数据的利器,熟练掌握可以解决很多复杂的类型问题。


总结

递归类型是 TypeScript 类型系统中的高级特性。

  • 自引用:类型定义中引用自身
  • 树形结构:表达无限嵌套的数据
  • 深度转换:实现深度只读、深度可选等工具类型
  • 链式结构:表示链表等线性递归结构

建议:在处理嵌套数据时,优先考虑使用递归类型来保证类型安全。