Как улучшить сигнатуру функции, чтобы избежать передачи undefined?
Что проверяют
Понимаете ли вы, откуда в вызовах берётся undefined и как устроить сигнатуру, чтобы ошибка ловилась при компиляции, а не в рантайме. Часто за вопросом стоит функция с пятью позиционными параметрами и вызов вида fn(a, undefined, undefined, true). Следом спрашивают, чем undefined отличается от null для значений по умолчанию.
Как отвечать
- объект параметров: вместо длинного позиционного списка один объект,
function createUser({ name, role = "user", notify = false }). ВызовcreateUser({ name, notify: true })читается без счёта позиций, и пропускать ничего не нужно; - значения по умолчанию: срабатывают только на
undefined,nullпроходит как есть. Чтобы деструктуризация не падала на вызове без аргумента, задайте= {}и для самого объекта; - TypeScript: обязательный параметр без
?не даёт вызвать функцию без аргумента, опциональные ставятся в конец. СexactOptionalPropertyTypesвtsconfig.jsonполеrole?: stringуже не принимает явныйundefined, и отсутствие поля отличается от переданногоundefined; - размеченные объединения: если параметры имеют смысл только вместе, тип
{ mode: "range"; from: Date; to: Date } | { mode: "single"; date: Date }запрещает несогласованные комбинации; ??вместо||: внутри телаlimit ?? 10сохранит законный0, аlimit || 10его заменит;- граница с внешним миром: данные из API и форм TypeScript в рантайме не проверяет, поэтому там нужна схема через
zodили аналог, иначе строгая сигнатура обманывает.
Где спотыкаются
Считают, что значение по умолчанию сработает и на null. Параметр (limit = 10) при вызове fn(null) получит null, а не 10.
Добавляют ручные проверки if (x === undefined) в каждую функцию вместо того, чтобы запретить такой вызов типом.
Делают все поля опциональными «на всякий случай». Тогда TypeScript перестаёт ловить забытые аргументы, и undefined снова доезжает до середины логики.