Recursive Types
Recursive Types (рекурсивные типы) — типы TypeScript, которые ссылаются на себя в своём определении, позволяя описывать древовидные и вложенные структуры данных произвольной глубины.
Зачем нужно
Многие структуры данных рекурсивны по природе: деревья, JSON, файловые системы, вложенные комментарии, AST. TypeScript поддерживает рекурсию в type alias (с TS 3.7+) и в интерфейсах, что позволяет точно типизировать такие структуры.
Где используется
- JSON-тип (
string | number | boolean | null | JsonArray | JsonObject) - Дерево категорий, меню с вложенностью
- Узлы AST и компиляторов
- Рекурсивные utility types:
DeepPartial,DeepReadonly - Файловая система: директории содержат файлы и директории
Основной контент
Рекурсивный интерфейс
interface TreeNode<T> {
value: T;
children: TreeNode<T>;
}
const tree: TreeNode<string> = {
value: "root",
children: [
{
value: "branch-1",
children: [
{ value: "leaf-1", children: },
{ value: "leaf-2", children: },
],
},
{ value: "branch-2", children: },
],
};
function traverse<T>(node: TreeNode<T>, fn: (value: T) => void): void {
fn(node.value);
node.children.forEach((child) => traverse(child, fn));
}
JSON-тип
type JsonPrimitive = string | number | boolean | null;
type JsonObject = { [key: string]: JsonValue };
type JsonArray = JsonValue;
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
const data: JsonValue = {
name: "Alice",
age: 30,
tags: ["admin", "user"],
address: {
city: "Moscow",
coords: [55.75, 37.62],
},
};
Рекурсивные utility types
// DeepPartial — все поля на всех уровнях опциональные
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
// DeepReadonly — все поля на всех уровнях readonly
type DeepReadonly<T> = T extends (infer U)
? ReadonlyArray<DeepReadonly<U>>
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
interface Config {
db: { host: string; port: number };
cache: { ttl: number; maxSize: number };
}
type PartialConfig = DeepPartial<Config>;
// { db?: { host?: string; port?: number }; cache?: { ttl?: number; maxSize?: number } }
type ImmutableConfig = DeepReadonly<Config>;
// Все поля readonly на всех уровнях
Категории с вложенностью
interface Category {
id: string;
name: string;
subcategories: Category;
}
function findById(categories: Category, id: string): Category | undefined {
for (const cat of categories) {
if (cat.id === id) return cat;
const found = findById(cat.subcategories, id);
if (found) return found;
}
return undefined;
}
Ограничение глубины рекурсии (TS 4.1+)
// Тип для ограниченной глубины (Depth = 0 → never)
type Prev = [never, 0, 1, 2, 3, 4, 5];
type DeepPartialWithDepth<T, D extends number = 5> = [D] extends [never]
? never
: T extends object
? { [K in keyof T]?: DeepPartialWithDepth<T[K], Prev[D]> }
: T;
Частые ошибки
- Бесконечная рекурсия типа — TypeScript ограничивает глубину рекурсии; слишком глубокие типы вызовут ошибку
Type instantiation is excessively deep. - type alias без объектного wrapper —
type A = Aвызовет ошибку; рекурсия должна быть через объект или условный тип. - Не обрабатывать базовый случай — в функциях, обходящих рекурсивный тип, обязательно нужна проверка на пустой массив/null-терминал.
- Конфузить структурную и рекурсивную совместимость — TypeScript принимает более узкие реализации, но не уберегает от зацикливания в рантайме.