Skip to main content

Рекурсивний тип

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

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.