TypeScript
100 питань
Як встановити TypeScript у проект?
•npm i -D typescript.
•npx tsc --init створить tsconfig.json.
•Додай скрипти: tsc, tsc -w.
•Для Node: npm i -D ts-node.
•Перевір: npx tsc -v.
Що таке tsconfig.json і за що він відповідає?
•Це конфігурація компілятора.
•target/module визначають вихідний JS.
•strict включає строгі перевірки.
•include/exclude керують файлами.
•paths/baseUrl налаштовують алиаси.
Чим відрізняються type і interface?
•interface розширюється через extends.
•interface підтримує declaration merging.
•type зручний для union/intersection.
•Для об'єктів частіше беруть interface, але це не правило.
•У реальності вибирай за задачею і стилем команди.
Що таке strict mode у TypeScript і чому його варто вмикати?
•strict включає набір строгих флагів.
•Ловить помилки з null/undefined і типами.
•Покращує автодоповнення і рефакторинг.
•Підвищує якість коду у великих проектах.
•Вмикай поступово, якщо проект старий.
Що таке any і чому його варто уникати?
•any вимикає перевірку типів.
•Помилки переходять у runtime.
•Замість any використовуйте unknown, generics або точні типи.
•Для міграції можна тимчасово використовувати any, але з планом прибрати.
•Можна обмежити через eslint/tsconfig.
Чим відрізняється unknown від any?
•unknown безпечніше: його не можна використовувати без перевірки.
•Вимагає narrowing (typeof, in, instanceof).
•Підходить для зовнішніх даних (JSON).
•any — 'дірка' у типах.
•З unknown помилки ловляться на етапі компіляції.
Як типізувати результат JSON.parse?
•JSON.parse повертає any.
•Використовуй unknown і валідовуй структуру.
•Створюй type guard або схему (zod).
•Після валідacji — приведи до потрібного типу.
•Не роби сліпий as YourType без перевірки.
Що таке type narrowing і як він працює?
•Це звуження типу у гілках коду.
•Використовуй typeof для примітивів.
•Використовуй in/instanceof для об'єктів/класів.
•Користувацькі type guards також звужують тип.
•Допомагає писати код без зайвих кастів.
Як написати функцію type guard?
•Функція повертає вираз виду x is T.
•Внутрішньо перевіряй поля і типи.
•Використовуй для unknown/union.
•Тримай перевірки простими і покривай тестами.
•Валідація схемою (zod) часто надійніша.
Що таке union types і де вони корисні?
•Union: A | B.
•Добре для обмежених наборів значень.
•Часто використовують з дискримінуючим полем.
•Вимагає narrowing перед доступом до специфічних полів.
•Покращує виразність API.
Що таке intersection types і коли їх використовувати?
•Intersection: A & B.
•Об'єднує властивості кількох типів.
•Зручно для композицій і міксинів.
•Обережно з конфліктуючими полями.
•Часто зустрічається у типах бібліотек.
Що таке literal types і навіщо вони потрібні?
•Типи літералів: 'GET', 200, true.
•Основи для union з конкретних значень.
•Зручно для статусів, ролей, режимів.
•Працюють разом з as const.
•Допомагають уникнути помилок при друку.
Навіщо потрібен as const?
•Робить літерали незмінними і вузькими.
•Перетворює масив у readonly tuple.
•Корисно для конфігів і словників.
•Спрощує виведення типів.
•Часто використовується з satisfies.
Що таке satisfies і чим він краще 'as'?
•satisfies перевіряє відповідність типу, але зберігає вивід.
•Не 'зажимає' значення як as.
•Корисно для конфігів і мапінгів.
•Допомагає ловити помилки ключів/полів.
•Доступно в TS 4.9+.
Як типізувати об'єкт-словник з фіксованими ключами?
•Використовуй Record<K, V>.
•K може бути union літералів.
•Для часткових ключів — Partial<Record<...>>.
•Для перевірки без втрати виводу — satisfies.
•Для динаміки — index signature.
Що таке index signature і коли він потрібен?
•{[key: string]: Value}.
•Для об'єктів з невідомими ключами.
•Обмежує тип усіх значень.
•Може конфліктувати з конкретними полями — обережно.
•Іноді краще Map<string, V>.
Як типізувати масив з фіксованою структурою (tuple)?
•Tuple: [number, string].
•Можна робити readonly tuple через as const.
•Для іменованих елементів: [id: string, value: number].
•Зручно для пар key-value.
•Для великих структур краще interface.
Що таке generics і навіщо вони потрібні?
•Дозволяють параметризувати типи.
•Наприклад: function first<T>(a: T[]): T.
•Убирають дублювання коду.
•Можна обмежувати T через extends.
•Покращують типізацію бібліотек.
Як обмежувати generics через extends?
•<T extends { id: string }>.
•Тоді у T гарантовано є id.
•Можна обмежувати union'ом.
•Для ключів використовуйте extends keyof.
•Обмеження покращують автодоповнення.
Як типізувати функцію getProperty(obj, key)?
•<T, K extends keyof T>(obj: T, key: K) => T[K].
•keyof дає набір ключів.
•T[K] повертає тип значення за ключем.
•Працює і з optional полями.
•Це канонічний приклад generics.
Що таке keyof і чому він корисний?
•keyof T дає union ключів типу.
•Дозволяє безпечно індексувати.
•Зручний у утилітах Pick/Omit.
•Допомагає створювати типобезпечні API.
•Працює разом з mapped types.
Що таке mapped types?
•Це типи виду { [K in keyof T]: ... }.
•Дозволяють трансформувати поля.
•На них основані Partial/Required/Readonly.
•Можна змінювати модифікатори: -? / +readonly.
•Потужний інструмент для бібліотек.
Як працює Partial<T> і коли його використовувати?
•Робить усі поля optional.
•Зручно для патч-об'єктів (update).
•Небезпека: втрачається гарантія наявності полів.
•Часто краще Partial<Pick<T, ...>>.
•Для форми введення — хороший варіант.
У чому різниця між Pick і Omit?
•Pick бере підмножину полів.
•Omit виключає поля.
•Зручно для DTO і публічних моделей.
•Уникайте дублювання структур.
•Комбінуйте з Partial/Required.
Що таке Record<K, V> і де він корисний?
•Record створює об'єкт з ключами K і значеннями V.
•Зручно для словників і маппінгів.
•K може бути union'ом.
•Для неповних ключів використовуйте Partial<Record<...>>.
•Для суворої перевірки ключів — satisfies.
Як типізувати функції зворотного виклику (callbacks)?
•Опиши сигнатуру: type Fn = (x: number) => string.
•Для подій — використовуй типи бібліотеки (React, DOM).
•Для async — Promise<...>.
•Не забувай про опційні параметри.
•Зроби явним повернене значення.
Як типізувати Promise та async/await?
•async функція повертає Promise<T>.
•Вкажи тип результату: Promise<User>.
•await розкриває Promise.
•Обробляй помилки через try/catch.
•Не повертай any з async-ланцюгів.
Що таке overloads (перевантаження) функцій?
•Можна оголосити кілька сигнатур.
•Реалізація одна, але типи різні.
•Корисно для функцій з різними режимами.
•Перевантаження мають бути сумісними.
•Іноді простіше використовувати union і narrowing.
Чим відрізняється type assertion (as) від реальної перевірки типів?
•as не перевіряє runtime.
•Він каже компілятору: 'повір'.
•Може приховати баги.
•Краще спочатку валідовувати дані.
•Використовуй as тільки коли точно знаєш структуру.
Що таке non-null assertion (!) і чому він небезпечний?
•x! говорить, що x не null/undefined.
•Компилятор перестає скаржитись.
•В runtime може впасти, якщо ти помилився.
•Краще перевірка if (x) або optional chaining.
•Використовуй рідко і усвідомлено.
Як працює optional chaining (?.)?
•Дозволяє безпечно звертатися до вкладених полів.
•obj?.a?.b поверне undefined, якщо щось відсутнє.
•Працює з викликом функцій: fn?.().
•Не замінює бізнес-валидацію.
•Зручно для UI та даних з API.
Як працює nullish coalescing (??)?
•a ?? b бере b тільки якщо a null/undefined.
•Не плутати з 0/''/false як ||.
•Відмінно для дефолтів.
•Часто комбінують з optional chaining.
•Розумій різницю між || і ??.
Як типізувати помилки в catch у строгому режимі?
•В TS catch-перемінна часто unknown.
•Перевір: err instanceof Error.
•Логуй безпечно: String(err).
•Для бібліотек роби свої типи помилок.
•Не передбачай структуру без перевірки.
Що таке discriminated union і як його проектувати?
•Union з полем-тегом: type: 'a' | 'b'.
•У switch по тегу тип автоматично звужується.
•Зручно для подій і стану.
•Додавай exhaustive check (never).
•Це один із кращих патернів TS.
Як зробити exhaustive check у switch?
•У default присвой змінну типу never.
•Якщо з’явиться новий варіант union — компілятор скаржитиметься.
•Приклад: const _exhaustive: never = x.
•Допомагає підтримувати код.
•Особливо корисно в reducer'ах.
Як типізувати класи та їх поля у TypeScript?
•Вказуй модифікатори: public/private/protected.
•Оголошуй поля заздалегідь або в constructor.
•Використовуй readonly для незмінних.
•Параметр-властивість: constructor(private api: Api) {}.
•Не зловживай класами, якщо достатньо функцій.
Що таке abstract class і interface: що обрати?
•interface описує контракт без реалізації.
•abstract class може містити спільну реалізацію.
•У TS можна реалізувати кілька interface, але успадковувати один клас.
•Для 'портів' та API зазвичай достатньо interface.
•Abstract зручний для базових компонентів/шаблонів.
Як типізувати this у методах і функціях?
•У методів класів this виводиться автоматично.
•Для функцій можна вказати this-параметр: function f(this: X) {}.
•Стрілкові функції беруть this із зовнішнього контексту.
•У callbacks часто втрачається this — біндуй або використовуй стрілки.
•Правильна типізація ловить помилки.
Як працює enum і чому часто радять union замість enum?
•enum генерує runtime-код (зазвичай).
•Може ускладнити bundle.
•Union літералів простіше і tree-shakable.
•Якщо потрібно runtime-значення — можна enum або const object.
•const enum прискорює, але небезпечний для бібліотек.
Що таке const enum і в чому його ризики?
•const enum інлайниться у код.
•Зменшує runtime-накладні.
•Може ламатися при збірці бібліотек і різних налаштуваннях.
•Не завжди сумісний з babel/transpileOnly.
•Часто краще об'єкт + as const.
Як типізувати функції вищого порядку (HOF)?
•Обґрунтовуй вхідний і вихідний тип.
•Зберігай сигнатуру через generics.
•Для декораторів функцій використовуй <T extends (...a: any[]) => any>.
•Повертай Parameters<T> і ReturnType<T>.
•Це дає сильну типізацію.
Що таке ReturnType і Parameters і коли вони потрібні?
•ReturnType<F> бере тип результату функції.
•Parameters<F> бере кортеж аргументів.
•Корисно для обгорток і проксі.
•Знижує дублювання.
•Працює тільки з типами функцій.
Як типізувати систему подій (event emitter) безпечно?
•Зроби map: EventName -> payload type.
•on<K extends keyof Events>(name: K, cb: (p: Events[K]) => void).
•emit аналогічно.
•Так ти уникнеш помилок типів у подіях.
•Гарний патерн для фронту і ноди.
Як типізувати API-клієнт і відповіді сервера?
•Визнач DTO типи для відповідей.
•Використовуй runtime-валидацію для зовнішніх даних.
•Для fetch роби функцію request<T>() що повертає Promise<T>.
•Ніколи не довіряй серверу без перевірок.
•Логуй і обробляй помилки статусів.
Як типізувати fetch і обробку JSON?
•const res = await fetch(url).
•Перевіряй res.ok.
•const data: unknown = await res.json().
•Валідируй data і потім приведи до типу.
•Таймаути/ретраї додавай окремо.
Як у TS правильно працювати з DOM-типами?
•Включи lib: ['DOM'].
•Використовуй querySelector з generic: document.querySelector<HTMLDivElement>('...').
•Перевіряй на null.
•Для подій використовуй MouseEvent/KeyboardEvent.
•Не кастуй без необхідності.
Як типізувати addEventListener і обробники?
•element.addEventListener('click', (e) => { ... }).
•e має тип MouseEvent (для кліка).
•Для window/document типи теж виводяться.
•Якщо потрібно, уточнюй target через instanceof.
•Не використовуй any для подій.
Як типізувати React-компоненти на TypeScript?
•Props через interface/type.
•function Component(props: Props) { ... }.
•Для children: React.ReactNode.
•Для подій: React.ChangeEvent<HTMLInputElement>.
•Не завжди потрібен React.FC.
Коли використовувати React.FC, а коли ні?
•React.FC додає children за замовчуванням.
•Іноді це небажано.
•Типізація defaultProps відрізняється.
•Прості функції з Props зазвичай достатньо.
•Обирай єдиний стиль у проекті.
Як типізувати useState у React?
•useState<number>(
•фіксує тип.
•Для nullable: useState<User | null>(null).
•Для складних об'єктів краще інтерфейс.
•Лінива ініціалізація: useState(() => compute()).
•Уникай any у стані.
Як типізувати useRef?
•useRef<HTMLDivElement | null>(null).
•Перевіряй ref.current на null.
•Для mutable значення: useRef<number>(0).
•Не використовуй ref як стан.
•Для callbacks можна useCallback ref.
Що таке declaration merging і де воно зустрічається?
•interface може об'єднуватися за ім'ям.
•Зручно для розширення глобальних типів.
•Часто використовується для доповнення Window/NodeJS.ProcessEnv.
•Може бути джерелом конфліктів.
•Контролюй, де оголошуєш глобальні типи.
Як розширити тип Window або ProcessEnv?
•Створи d.ts файл у проекті.
•declare global { interface Window { ... } }.
•Для env: declare namespace NodeJS { interface ProcessEnv { ... } }.
•Не забудь export {} у файлі.
•Слідкуй, щоб include бачив цей файл.
Що таке .d.ts файли і навіщо вони потрібні?
•Це файли декларацій типів.
•Потрібні для JS-бібліотек без типів.
•Можуть описувати API без реалізації.
•Використовуються DefinitelyTyped (@types/*).
•Підключаються через types/include.
Як додати типи для JS-бібліотеки без @types?
•Спочатку перевір, чи немає вбудованих типів.
•Якщо ні — створи мінімальний .d.ts з declare module 'lib'.
•Постепенно уточнюй типи за мірою використання.
•Можна написати wrapper API зі строгими типами.
•Для складних випадків — генерація з OpenAPI/JSON schema.
Що таке moduleResolution і коли його змінювати?
•Визначає, як TS шукає модулі.
•NodeNext потрібен для ESM/Node 16+.
•Bundler підходить для Vite/webpack.
•Classic майже не потрібен.
•Вибір залежить від рантайму та збірщика.
Як налаштувати алиаси шляхів (paths) у tsconfig?
•Вкажи baseUrl.
•Додай paths: { '@/*': ['src/*'] }.
•Налаштуй збірщик (webpack/vite) аналогічно.
•Налаштуй jest/vitest алиаси теж.
•Інакше в runtime алиаси не працюватимуть.
Чому TS проходить, а в runtime модуль не знаходиться?
•TS перевіряє шляхи за tsconfig paths.
•Але Node/бандлер може не знати про алиаси.
•Потрібно налаштувати резолвер у збірщику/тестах.
•Для Node можна використовувати tsconfig-paths.
•Завжди перевіряй конфігурацію runtime.
Що таке isolatedModules і коли він потрібен?
•Потрібен для transpile через Babel/'один файл'.
•Забороняє деякі конструкції (наприклад, const enum).
•Допомагає сумісності зі збірщиками.
•У Next.js зазвичай вмикають.
•Краще тримати увімкненим у фронтенд-проектах.
У чому різниця між tsc і babel/swc при збірці TS?
•tsc виконує типову перевірку і може емитити JS.
•Babel/SWC швидко транспілюють, але не перевіряють типи.
•У таких схемах типчек запускають окремо (tsc --noEmit).
•Next/Vite часто використовують SWC/Babel.
•Перевірка типів потрібна в CI.
Як увімкнути перевірку типів без генерації JS?
•Запусти: tsc --noEmit.
•Або постав noEmit: true в tsconfig.
•Зручно для проектів, де збірку робить інший інструмент.
•В CI роби окремий крок typecheck.
•Помилки типів тоді не потраплять у прод.
Що таке source maps і як вони допомагають?
•Пов’язують скомпільований JS із вихідним TS.
•Спрощують відладку в браузері і Node.
•Вмикається sourceMap: true.
•У проді іноді відключають через розмір і безпеку.
•Для Sentry корисні.
Як правильно типізувати конфіг-об’єкти?
•Використовуй const + as const для літералів.
•Для перевірки структури застосовуй satisfies.
•Уникай any.
•Зберігай конфіг у одному місці, не розмазуй.
•Для env-змінних роби явне перетворення типів.
Як типізувати process.env і позбавитись 'string | undefined'?
•В runtime змінні можуть відсутні.
•Спершу валідируй наявність (наприклад, через функцію requireEnv).
•Після перевірки можна повернути string.
•Краще робити єдиний шар конфігурації.
•Для строгої схеми використовуй zod/valibot.
Як типізувати функцію, яка кидає помилку (never)?
•Функції, що завжди кидають/завершують процес, повертають never.
•Приклад: function fail(msg: string): never { throw new Error(msg) }.
•Зручно в exhaustive checks.
•Допомагає аналізу потоку.
•Покращує читабельність помилок.
Що таке типи шаблонних літералів?
•Типи рядків із шаблоном: `user:${string}`.
•Корисно для побудови ключів і маршрутів.
•Працюють з union і mapped types.
•Ускладнюють типи, використовуйте обережно.
•Відмінно для подій/ID-шників.
Як типізувати рядки формату UUID/id?
•Повної валідації типами зробити не можна.
•Можна створити брендований тип: type UUID = string & { __brand: 'UUID' }.
•Перетворення лише після перевірки regex.
•Це підвищує безпеку API.
•Без runtime перевірки бренд безглуздо.
Що таке branded types і навіщо вони потрібні?
•Це спосіб розрізняти однакові примітиви (string vs Email).
•Робиться через перетин із 'маркером'.
•Вимагає фабрики/валідації.
•Зменшує помилки при передачі параметрів.
•Корисно в доменній моделі.
Як типізувати функції-валідатори?
•Повертають boolean і type predicate.
•Наприклад: (x: unknown): x is User.
•Тримай перевірки на одному рівні, без side effects.
•Валідація схемою часто простіша.
•Додай тести на валідатори.
Що таке conditional types?
•Типи виду T extends U ? X : Y.
•Використовуються для розумних утиліт.
•Можуть розподілятися по union.
•Іноді сильно сповільнюють компіляцію.
•Застосовуй переважно для бібліотечного коду.
Що таке infer у conditional types?
•infer дозволяє 'витягнути' частину типу.
•Наприклад, отримати тип елемента масиву.
•Використовується в ReturnType/Parameters.
•Потужно, але важче читати.
•Пиши приклади і тестуй типи.
Як отримати тип елемента масиву (array element type)?
•Для масиву T[] елемент — T.
•Для довільного: type Elem<A> = A extends (infer T)[] ? T : never.
•Для readonly масивів враховуй readonly.
•Корисно в узагальнених утилітах.
•Не забувай про tuple.
Що таке utility types Required/Readonly і навіщо?
•Required робить поля обов’язковими.
•Readonly робить поля тільки для читання.
•Використовуй для публічних інтерфейсів і імутабельності.
•Є ReadonlyArray і readonly tuple.
•Зменшує кількість мутацій у коді.
Як типізувати partial update об’єкта безпечно?
•Не роби Partial<T> на все підряд.
•Обмеж набір полів: Partial<Pick<T, 'a' | 'b'>>.
•Для заборони деяких полів — Omit.
•Валідируй дані перед застосуванням.
•Для API краще окремий DTO.
Як типізувати функцію merge без втрати типів?
•<A extends object, B extends object>(a: A, b: B): A & B.
•Але враховуй конфліктуючі ключі.
•В runtime застосовуй Object.assign/spread.
•Для глибокого merge типи складніші.
•Іноді краще явний опис результату.
Як працювати з readonly і мутаціями об'єктів?
•Readonly забороняє присвоювання полям.
•Для змін створіть копію через {...obj}.
•Для масивів: [...arr].
•Використовуйте імутабельні патерни у стані.
•Для глибоких структур потрібна дисципліна/immer.
Як типізувати об'єкт, який може бути null?
•Використовуйте union: T | null.
•Перевіряйте if (x == null) return.
•Уникайте ! для обходу.
•Для ланцюгового доступу використовуйте ?.
•Продумайте, де саме допускається null.
Як правильно обрати між null і undefined?
•undefined частіше означає 'відсутність значення' за замовчуванням.
•null часто використовується як явне 'порожньо'.
•Тримайте єдиний стиль у проекті.
•В API краще документувати.
•У strictNullChecks обидва вимагають обробки.
Як типізувати функції з опційними параметрами?
•Параметр: foo(x?: string).
•Або foo(x: string | undefined).
•Враховуйте, що x може бути undefined.
•Встановлюйте дефолт: function foo(x: string = 'a').
•Не робіть багато опційних підряд — краще об'єкт параметрів.
Як типізувати 'options object' замість довгого списку аргументів?
•Створіть interface Options { ... }.
•function f(opts: Options) {}.
•Надання дефолтів через деструктуризацію.
•Це зручніше для розширення API.
•Дозволяє робити Partial<Options> для дефолтів.
Як працює структурне типізування в TypeScript?
•В TS важлива структура, а не ім'я типу.
•Два типи сумісні, якщо поля збігаються.
•Це зручно, але може приховати помилки у домені.
•Branded types допомагають розрізняти.
•Завжди думайте про межі системи.
Що таке номінальне типізування і як імітувати його в TS?
•Номінальне — сумісність за ім'ям типу.
•В TS можна імітувати через бренди.
•type UserId = string & { __brand: 'UserId' }.
•Створіть функцію-конструктор після валідації.
•Це зменшує випадкові підстановки.
Як типізувати функції роботи з масивами (map/filter/reduce) без any?
•TS автоматично виводить типи колбеків.
•Слідкуйте, щоб вихідний масив був типізований.
•Для reduce вказуйте початкове значення і тип акумулятора.
•Уникайте reduce без initial — можуть виникнути проблеми.
•Для filter іноді потрібен type guard.
Як зробити type guard для filter?
•Приклад: const isDefined = <T>(x: T | undefined): x is T => x !== undefined.
•Тоді arr.filter(isDefined) видалить undefined.
•Аналогічно для null.
•Зручно у пайплайнах даних.
•Створюйте такі утиліти в одному місці.
Що таке satisfies для масивів і словників?
•Можна перевірити тип масиву, не втрачаючи literal types.
•Приклад: const routes = [...] satisfies Route[].
•Компилятор перевірить структуру елементів.
•Але збереже конкретику значень.
•Гармонійний баланс між 'як є' і строгим перевіркою.
Як прискорити компіляцію TypeScript у великому проекті?
•Увімкни incremental.
•Використовуй project references.
•Зменшуй include і виключи build/output папки.
•Уникай складних conditional types у гарячих місцях.
•Онови TS до актуальної версії.
Що таке project references і навіщо вони потрібні?
•Це розбиття монорепо на проекти TS.
•Прискорює збірку і typecheck.
•Дозволяє 'інкрементально' перевіряти лише змінені частини.
•Вимагає composite: true.
•Добре для великих кодових баз.
Як налаштувати monorepo з TypeScript?
•Використовуй менеджер workspace (pnpm/yarn/npm).
•Створи базовий tsconfig і наслідуйся від нього.
•Увімкни project references.
•Слідкуй за єдиними версіями TS і типів.
•Налаштуй лінтер і форматтер однаково.
Як правильно типізувати публічний API бібліотеки?
•Експортуй лише потрібні типи.
•Не експортуй внутрішні деталі.
•Стабілізуй contract і версіонуй.
•Генеруй d.ts (declaration: true).
•Тестуй типи через dtslint або tsd.
Як перевірити типи у CI?
•Додай скрипт: npm run typecheck.
•Запускай tsc --noEmit.
•Окремо запускай eslint/test.
•Не покладайся на IDE.
•Зроби перевірку обов’язковою перед merge.
Як типізувати проект Node.js на TypeScript?
•Налаштуй module і moduleResolution під ESM/CJS.
•Додай @types/node.
•Обери раннер: ts-node, tsx або збірку через tsc.
•Слідкуй за шляхами/аліасами.
•Перевір запуск у проді (без ts-node).
У чому різниця між ESM і CommonJS у контексті TypeScript?
•ESM використовує import/export у runtime.
•CJS використовує require/module.exports.
•TS може емитити обидва варіанти.
•У Node важливі package.json type і розширення .mts/.cts.
•Помилки частіше через змішування систем.
Як налаштувати TypeScript для Node ESM (NodeNext)?
•module: NodeNext і moduleResolution: NodeNext.
•У package.json постави type: module.
•Використовуй розширення/exports правильно.
•Для типів можна .d.ts або .mts.
•Перевір збірку і запуск разом.
Як типізувати Express middleware?
•Встанови @types/express.
•Типи Request, Response, NextFunction.
•Розширюй Request через declaration merging, якщо додаєш поля.
•Робіть явні типи для body/query/params.
•Валідовуй вхідні дані.
Як типізувати DTO і валідацію вхідних даних?
•Типи TS не валідовують runtime.
•Використовуй zod/yup/class-validator.
•Схема має бути джерелом істини.
•Генеруй тип із схеми, якщо бібліотека дозволяє.
•Тримай валідатор на межі системи (API).
Як безпечно працювати з зовнішніми даними: JSON, API, localStorage?
•Вважайте все зовнішнє як unknown.
•Валідовуйте структуру (schema/type guards).
•Обробляйте відсутність полів і версії.
•Логуйте помилки парсингу та фолбеки.
•Не використовуйте as без перевірок.
Як типізувати localStorage і перетворення типів?
•localStorage зберігає рядки.
•Створюйте функції getJSON<T>() з unknown + валідатором.
•Для чисел/булевих — явне перетворення.
•Обробляйте відсутність ключа.
•Версіонізуйте формат збереження.
Як правильно типізувати помилки для доменної логіки?
•Створюйте свої класи помилок (extends Error).
•Додавайте код/тип причини.
•Ловіть і маппіть у верхньому шарі (API/UI).
•Не кидайте рядками.
•Документуйте, які помилки можливі.
Які типові антипатерни в TypeScript слід уникати?
•any і ланцюжки as as.
•! замість перевірок.
•Складні типи заради типів без вигоди.
•Дублювання DTO замість утиліт Pick/Omit.
•'Магія' в типах без тестів і прикладів.
Як писати підтримувані типи: практичні правила?
•Спочатку прості типи, ускладнюйте за потреби.
•На межах системи валідовуйте runtime.
•Не приховуйте проблему через any/as.
•Використовуйте discriminated unions для станів.
•Підтримуйте єдиний стиль і типчекі в CI.