TypeScript 递归类型
递归类型是一种引用自身的类型,在处理树结构、嵌套数据时非常有用。
TypeScript 支持递归类型定义,可以表达无限深度的数据结构。
为什么需要递归类型
在现实世界中,数据结构往往是嵌套的。
例如,文件系统有文件夹和子文件夹,组织架构有部门和子部门,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);
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));
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);
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);
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));
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"));
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 类型系统中的高级特性。
- 自引用:类型定义中引用自身
- 树形结构:表达无限嵌套的数据
- 深度转换:实现深度只读、深度可选等工具类型
- 链式结构:表示链表等线性递归结构
建议:在处理嵌套数据时,优先考虑使用递归类型来保证类型安全。
