- Главная
- Typescript
- Ошибки компилятора TypeScript: упражнение
Ошибки компилятора TypeScript: упражнение
В предыдущих уроках мы разобрали, как компилятор TypeScript помогает ловить ошибки ещё до запуска кода, и как флаги вроде --noEmit встраиваются в рабочий процесс разработки. Теперь пришло время закрепить это на практике: перед вами небольшой файл с профилями пользователей, в котором компилятор находит сразу несколько проблем. Ваша задача — не меняя логику программы, объявить недостающие типы так, чтобы tsc перестал ругаться.
Что нужно сделать в упражнении
Представьте, что вам достался небольшой модуль интернет-магазина, отвечающий за работу с профилями покупателей. Часть функций в этом модуле не имеет явных типов параметров, а одна из функций должна возвращать «публичную» версию профиля без чувствительных данных. При запуске компилятора в строгом режиме (strict) все эти места подсвечиваются как ошибки, потому что TypeScript не может понять, с какими значениями работает код.
Файл с ошибками
function updateProfile(id, changes) {
const profile = profiles.find((p) => p.id === id);
if (!profile) {
throw new Error('Profile not found');
}
return { ...profile, ...changes };
}
function getDisplayName(user) {
return user.nickname ?? user.fullName;
}
function toPublicProfile(profile) {
const { passwordHash, email, ...rest } = profile;
return rest;
}
Если скопировать этот файл в проект с включённым --strict, компилятор выдаст сразу несколько ошибок вида «Parameter implicitly has an any type». Это значит, что TypeScript не смог вывести тип параметра самостоятельно и ждёт от нас явной аннотации.
Список необходимых правок
- Указать типы параметров
idиchangesв функцииupdateProfile - Типизировать параметр
userв функцииgetDisplayName - Учесть, что свойство
nicknameу пользователя может отсутствовать - Создать отдельный тип для «публичного» профиля без пароля и почты и использовать его в
toPublicProfile
Разбор ошибок компилятора
Прежде чем переходить к решению, полезно понять, откуда именно берётся каждая ошибка и какой приём из предыдущих уроков её закрывает. Дальше мы разберём три группы проблем: неявный any в параметрах, опциональные свойства и производные типы через Omit.
Ошибки implicit any в параметрах функций
Первым делом опишем форму профиля с помощью интерфейса — так у нас появится единый источник правды для типов во всём модуле.
interface Profile {
id: string;
fullName: string;
nickname?: string;
email: string;
passwordHash: string;
}
Теперь можно типизировать параметры функции updateProfile. Обратите внимание, что параметр changes — это не полный профиль, а лишь набор полей, которые нужно обновить, поэтому здесь пригодится знакомая по 19 уроку утилита Partial.
function updateProfile(id: string, changes: Partial<Profile>): Profile {
const profile = profiles.find((p) => p.id === id);
if (!profile) {
throw new Error('Profile not found');
}
return { ...profile, ...changes };
}
Тип Partial<Profile> делает все свойства Profile необязательными, поэтому в changes можно передать хоть одно поле, хоть все сразу — и TypeScript всё равно проверит, что переданные ключи и значения соответствуют исходному интерфейсу.
Опциональные свойства и union с undefined
Функция getDisplayName обращается к полю nickname, которое в интерфейсе Profile помечено вопросительным знаком. Это означает, что фактический тип этого свойства — string | undefined, и компилятор ожидает, что мы аккуратно обработаем случай отсутствия значения.
function getDisplayName(user: Profile): string {
return user.nickname ?? user.fullName;
}
Оператор нулевого слияния ?? проверяет именно null и undefined, поэтому он идеально подходит для такого сценария: если псевдонима нет, функция вернёт полное имя пользователя, а тип результата остаётся простым и предсказуемым — string.
Если поле объекта иногда отсутствует, старайтесь описывать его через необязательный модификатор ?, а не через ручное объединение с undefined в отдельной переменной. Так TypeScript сам подскажет во всех местах использования, что перед обращением к полю стоит подстраховаться, и не даст случайно забыть проверку.
Создание производных типов с помощью Omit
Последняя ошибка касается функции toPublicProfile: она должна возвращать профиль без пароля и почты, но отдельного типа для такой «урезанной» версии пока нет. Переписывать интерфейс с нуля не нужно — гораздо удобнее получить новый тип из уже существующего Profile с помощью утилиты Omit, знакомой по уроку про Partial, Pick и Omit.
type PublicProfile = Omit<Profile, 'passwordHash' | 'email'>;
function toPublicProfile(profile: Profile): PublicProfile {
const { passwordHash, email, ...rest } = profile;
return rest;
}
Omit<Profile, 'passwordHash' | 'email'> берёт исходный интерфейс Profile и убирает из него перечисленные через union ключи, формируя новый тип со всеми оставшимися полями. Благодаря этому переменная rest после деструктуризации автоматически подходит под тип PublicProfile, и компилятору не нужно ничего домысливать.
Пошаговое решение упражнения
Если собрать все правки вместе, получится компактный файл, в котором каждая функция и переменная имеют явный или корректно выведенный тип, а компилятор больше не находит поводов для ошибок.
interface Profile {
id: string;
fullName: string;
nickname?: string;
email: string;
passwordHash: string;
}
type PublicProfile = Omit<Profile, 'passwordHash' | 'email'>;
const profiles: Profile[] = [];
function updateProfile(id: string, changes: Partial<Profile>): Profile {
const profile = profiles.find((p) => p.id === id);
if (!profile) {
throw new Error('Profile not found');
}
return { ...profile, ...changes };
}
function getDisplayName(user: Profile): string {
return user.nickname ?? user.fullName;
}
function toPublicProfile(profile: Profile): PublicProfile {
const { passwordHash, email, ...rest } = profile;
return rest;
}
После этих изменений запуск tsc --noEmit завершается без единой ошибки: компилятор проверил типы, но, как и ожидалось, не создал ни одного файла на выходе. Это удобный способ убедиться, что решение действительно устраняет все проблемы, а не просто «затыкает» их приведением к any.
Возьмите за привычку запускать tsc --noEmit перед коммитом или сразу после решения похожих упражнений. Эта команда проверяет типы во всём проекте, ничего не компилируя, поэтому её удобно добавить в git-хук или в шаг CI — так ошибки не доедут до соседей по команде.
Итоги
В этом упражнении мы прошли путь от неявного any до полностью типизированного модуля: описали форму данных через интерфейс, использовали Partial для частичных обновлений, опциональные свойства для необязательных полей и Omit для создания «урезанной» версии типа.
В следующем уроке мы перейдём от отдельных упражнений к реальному проекту: разберём структуру full-stack приложения Event Me и наметим план его превращения в TypeScript-проект с типобезопасностью на бэкенде и фронтенде.