Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Рекурсивний тип». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Рекурсивний тип** - це тип чи інтерфейс, що посилається сам на себе у власному визначенні, наприклад `TreeNode<T>` зі списком `children?: TreeNode<T>[]`. **Ключове:** для строгої моделі дерева зручно використовувати дискриміновані union, а для дуже глибокої рекурсії - обмежувати глибину через параметр Depth.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## 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` або обмежуй глибину.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.