Модуль 2 · урок 14 з 15
Життя без any і ESLint
Останній пункт цілі звучить так: «налаштувати ESLint так, щоб будь-який any
валив збірку». Це найпростіше в реалізації й найнезручніше в житті — саме тому воно й працює
як навчальний прийом.
Чому any гірший за відсутність типів
const data: any = await res.json();
data.tasks.map(t => t.titel); // друкарська помилка — компілятор мовчить
data.foo.bar.baz; // такого немає — компілятор мовчить
const n: number = data.name; // рядок у число — компілятор мовчить
any — це не «тип невідомий», а «перевірок не буде». Найгірше те, що він
заразний: значення з any розтікається далі й вимикає перевірки в місцях,
де типи були в порядку.
unknown — чесна альтернатива
const data: unknown = await res.json();
data.tasks; // ❌ помилка: спершу доведи, що там є tasks
if (isTaskList(data)) { // type guard із попереднього уроку
data.forEach(t => t.title); // ✅ тепер тип відомий
}
Різниця в одному: any каже «роби що хочеш», unknown — «спочатку
переконайся». Обидва означають «тип невідомий», але лише другий змушує з цим щось зробити.
Налаштувати ESLint
ng add @angular/eslint
// eslint.config.js
export default tseslint.config(
{
files: ['**/*.ts'],
extends: [
eslint.configs.recommended,
...tseslint.configs.recommended,
...angular.configs.tsRecommended,
],
rules: {
// головне правило з таблиці IDP
'@typescript-eslint/no-explicit-any': 'error',
// корисні сусіди
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
'@typescript-eslint/explicit-function-return-type': ['warn', {
allowExpressions: true,
}],
'@angular-eslint/prefer-on-push-component-change-detection': 'error',
},
},
);
ng lint # перевірити
ng lint --fix # виправити те, що виправляється автоматично
@angular-eslint/prefer-on-push-component-change-detection закриває ще й вимогу
з модуля 3: лінтер сам нагадає, якщо десь забув ChangeDetectionStrategy.OnPush.
Одна строчка конфіга замість дисципліни.
Типові місця, де рука тягнеться до any
| Ситуація | Замість any |
|---|---|
| Відповідь HTTP | http.get<Task[]>(url) — дженерик прямо в запиті |
JSON.parse | unknown + type guard |
| Подія в шаблоні | ($event.target as HTMLInputElement).value — конкретний тип, а не any |
| Обʼєкт із довільними ключами | Record<string, string> |
| Функція, яка приймає що завгодно | Дженерик <T> |
Помилка в catch | Вона й так unknown: перевір через instanceof Error |
| Стороння бібліотека без типів | Свій .d.ts із мінімальним описом того, що реально використовуєш |
// обробка помилки без any
try {
await save();
} catch (e) { // e має тип unknown
const message = e instanceof Error ? e.message : 'Невідома помилка';
this.error.set(message);
}
Коли any все ж доводиться писати
// стороння бібліотека без типів, обгортаємо один раз
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- у legacy-віджета немає типів
declare const legacyWidget: any;
export function initWidget(el: HTMLElement): void {
legacyWidget.mount(el); // any лишився в одному рядку й не тече далі
}
Правило просте: якщо any неминучий — він має бути локальним і поясненим.
Один рядок із коментарем «чому» — це нормальна інженерна практика. Десять
eslint-disable по всьому проєкту — це вимкнений лінтер.
Лінтер у CI
# у пайплайні (модуль 9) перед тестами й збіркою
npm ci
npx ng lint
npx ng test --watch=false --browsers=ChromeHeadless
npx ng build --configuration production
Сенс не в тому, щоб карати за any, а в тому, щоб правило працювало без твоєї
участі. Локально можна забути запустити лінтер — пайплайн не забуде. У модулі 9 ти навчишся
читати його логи, коли він упаде.
- Увімкнути правило одразу, а не наприкінці.
- Кожен
unknownсупроводжувати type guard. - Дженерик у
http.get<T>писати завжди. - Прогнати
ng lintперед кожним комітом.
- Лишити правило на рівні
warn. eslint-disableна весь файл.- Замінити
anyнаobject— те саме, тільки незручніше. - Вимкнути лінтер у CI, бо «падає».