Система типов — Примеры
Пример 1 — Опечатка, которую нашёл бы только рантайм
На границе сумма корзины типизирована как any, так что свойство с опечаткой даёт NaN и уходит в продакшен. Опишите форму, которая функции действительно нужна, и скажите, что any делает со всеми производными значениями.
function totalCents(items: any) {
return items.reduce((sum, item) => sum + item.priceCents * item.quantity, 0);
}
totalCents([{ priceInCents: 300, quantity: 2 }]); // NaN, и об ошибке нигде не сообщается
Решение
type CartItem = { priceCents: number; quantity: number };
function totalCents(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.priceCents * item.quantity, 0);
}
totalCents([{ priceInCents: 300, quantity: 2 }]);
~~~~~~~~~~~~
error TS2353: Object literal may only specify known properties,
and 'priceInCents' does not exist in type 'CartItem'.
any не просто снимает проверку с этого значения — оно заразно для всего производного, поэтому массив и аккумулятор тоже перестают проверяться. Аннотирование границы — то, на чём система типов окупает свою цену: параметр и экспортируемый возвращаемый тип — это контракт, на который опирается вызывающий код, а всё внутри выводится из них. Проверка лишних свойств существует именно для этого случая, поскольку опечатка в имени члена появляется обычно там, где объектный литерал написан прямо против типизированного параметра. Там, где форму действительно нельзя знать — разобранная полезная нагрузка, сторонний обратный вызов, — unknown безопаснее: он принимает что угодно, но требует проверки перед использованием.
Пример 2 — null, доходящий до отрисовки
В записи пользователя удаление отмечено отметкой времени, которая допускает null, а функция подписи читает её без проверки. Скажите, что здесь меняет strict, и обработайте отсутствие.
type User = { id: string; name: string; deletedAt: string | null };
function label(user: User) {
return user.name.toUpperCase() + " " + user.deletedAt.slice(0, 10);
}
Решение
function label(user: User): string {
if (user.deletedAt === null) return user.name.toUpperCase();
return user.name.toUpperCase() + " " + user.deletedAt.slice(0, 10);
}
user.deletedAt.slice(0, 10)
~~~~~~~~~~
error TS18047: 'user.deletedAt' is possibly 'null'.
Со включённым strict null — обычный тип, а не что-то, прячущееся за каждым значением, поэтому аннотация string | null становится утверждением, которое компилятор проверяет, а не комментарием. Это превращает «cannot read properties of null» из падения в продакшене в ошибку на строке, которая его вызвала, и стоит одного раннего возврата; явное моделирование отсутствия — самая ценная часть строгого семейства, поэтому это и есть параметр, который стоит включать первым: аннотация и проверка — одно и то же решение, записанное один раз.