Рекурсивний тип
1) Базовий універсальний вузол дерева
javascript
interface TreeNode<T> {
id: string;
value: T;
children?: TreeNode<T>[]; // рекурсія: вузол містить список таких самих вузлів
}
const tree: TreeNode<number> = {
id: "root",
value: 0,
children: [
{ id: "a", value: 1 },
{ id: "b", value: 2, children: [{ id: "b1", value: 3 }] },
],
};
interfaceіtypeоднаково підходять для рекурсії; обирай те, що зручніше за стилем проекту.
2) Дискриміноване об'єднання (жорсткіший режим "лист/гілка")
javascript
type Leaf<T> = {
kind: "leaf";
id: string;
value: T;
};
type Branch<T> = {
kind: "branch";
id: string;
value: T;
children: Tree<T>[]; // рекурсивно
};
type Tree<T> = Leaf<T> | Branch<T>;
const t: Tree<string> = {
kind: "branch",
id: "root",
value: "root",
children: [{ kind: "leaf", id: "l1", value: "x" }],
};Такий підхід запобігає "листям з children" або "гілкам без children".
3) "Словникове" дерево (ключ → піддерево)
javascript
type DictTree<T> = {
value?: T;
children?: Record<string, DictTree<T>>; // рекурсія через Record
};
const categories: DictTree<null> = {
children: {
clothes: {
children: { shirts: { value: null }, pants: { value: null } },
},
},
};4) Зв'язки вгору (батько) - обережно з круговими посиланнями
javascript
interface LinkedNode<T> {
id: string;
value: T;
parent?: LinkedNode<T>; // вгору
children?: LinkedNode<T>[]; // вниз
}Такі структури незручно серіалізувати в JSON (кільця). У DTO зазвичай зберігають
parentId: stringзамістьparent.
5) Обхід/утиліти з точною типізацією
javascript
function traverse<T>(node: TreeNode<T>, visit: (n: TreeNode<T>) => void) {
visit(node);
node.children?.forEach(child => traverse(child, visit));
}
function mapTree<A, B>(node: TreeNode<A>, f: (v: A) => B): TreeNode<B> {
return {
id: node.id,
value: f(node.value),
children: node.children?.map(c => mapTree(c, f)),
};
}6) Рекурсивні утиліти (DeepReadonly / DeepPartial)
javascript
type DeepReadonly<T> = {
readonly [K in keyof T]: DeepReadonly<T[K]>;
};
type DeepPartial<T> = {
[K in keyof T]?: DeepPartial<T[K]>;
};Використання:
javascript
type FrozenTree<T> = DeepReadonly<TreeNode<T>>;
type PatchTree<T> = DeepPartial<TreeNode<T>>;7) Тип "вкладений об'єкт довільної глибини"
javascript
type Nested<T> = T | { [key: string]: Nested<T> };
const i18n: Nested<string> = {
app: { title: "Shop", nav: { home: "Home" } },
};8) Про "занадто глибокі" типи
Якщо отримуєш помилку виду "type instantiation is excessively deep and possibly infinite" у сильно рекурсивних mapped types, розбивай на рівні (параметр Depth) або спрощуй гілки (наприклад, зупиняйся на масивах/примітивах).
Приклад обмеження глибини:
javascript
type Dec<N extends number> = [
never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
][N]; // примітивний декремент для 0..9
type DeepReadonlyN<T, N extends number = 5> =
N extends 0 ? T :
T extends object
? { readonly [K in keyof T]: DeepReadonlyN<T[K], Dec<N>> }
: T;Підсумок
- Рекурсія працює і в
interface, і вtype. - Для строгої моделі дерева зручно використовувати дискриміновані union.
- Для словників -
Record<string, RecType>. - Для серіалізації уникай двонаправлених посилань; використовуй
parentId. - Для "глибоких" утиліт застосовуй
DeepReadonly/DeepPartialабо обмежуй глибину.
Коротка відповідь
Для співбесідиPremium
Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.