Необязательные параметры, значения по умолчанию и rest
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Одному вызову нужны все аргументы, другому хватит первого — и в JavaScript пропущенный аргумент молча становится undefined. TypeScript требует объявить такую вольность заранее: знаком ?, значением по умолчанию или ...rest. Взамен он считает аргументы за вас и не даёт забыть проверку — а заодно вводит правило, из-за которого необязательный параметр нельзя поставить перед обязательным.
В части I пропуск аргумента был обычным делом: функция получала undefined и как-то с этим жила. Компилятор смотрит иначе — он считает аргументы и не разрешает вызвать функцию не так, как она объявлена.
«Ожидалось аргументов: 2, получено: 1» — это TS2554. Обратите внимание, что исполнение всё равно состоялось и напечатало undefined Рико: диагностика ничего не останавливает. Из чистого JavaScript ровно такая строка и уехала бы в прод.
Если пропуск задуман, о нём нужно предупредить — знаком вопроса после имени параметра.
Счёт аргументов сошёлся, зато появилась диагностика внутри функции: TS18048, «Возможно, title имеет значение undefined». Дальше классический сюжет: предупреждение проигнорировали, первый вызов отработал, второй упал с TypeError — у undefined нет метода toUpperCase. Компилятор показал ровно ту строку, на которой программа и сломалась.
Знак вопроса не делает параметр волшебным, он добавляет к типу undefined. Внутри функции title — это string | undefined, то есть обычное объединение, которое сначала сужают.
Приём с заведомо неподходящим типом знаком по статье про сужение, но здесь он показывает не одну переменную, а всю сигнатуру: (name: string, title?: string | undefined) => string. Знак вопроса и | undefined стоят рядом не случайно — это две стороны одной записи. Снаружи аргумент можно не передавать, внутри его придётся проверить.
Знак вопроса ставится только в объявлении. На месте вызова не меняется ничего — вы просто не пишете аргумент.
Лишний аргумент в JavaScript молча пропадает. Компилятор считает и сверху тоже.
«Ожидалось аргументов: 1-2, получено: 3». Диапазон в тексте диагностики — это и есть подпись функции словами: один обязательный параметр, второй необязательный. Исполнение при этом напечатало инженер Рико, третий аргумент просто некуда было деть.
Проверять undefined в начале каждой функции надоедает. Обычно нужно другое: не передали — подставь разумное. Синтаксис тот же, что в части I, но в типах у него появляется приятный побочный эффект.
Внутри функции — ни одной диагностики: toUpperCase вызван без всякой проверки, и это законно. Единственная претензия компилятора — намеренная ложь про тип в последней строке, и она же называет сигнатуру целиком: (name: string, title?: string) => string. Знак вопроса на месте, а | undefined исчез: снаружи параметр по-прежнему можно пропустить, но внутрь undefined не доберётся, его перехватит умолчание. Третий вызов показывает, что явный undefined считается за «не передали» — это одно из немногих мест, где undefined и null ведут себя по-разному не на словах.
Тип параметра компилятор берёт из самого значения по умолчанию, аннотацию писать не нужно. Ошибиться типом при вызове всё равно не дадут.
Второй вызов — тот случай, ради которого стоит читать диагностику до запуска. «Аргумент типа string нельзя назначить параметру типа number» — а на исполнении ни ошибки, ни падения: пустая строка. repeat честно превратил "три" в NaN, округлил до нуля и повторил текст ноль раз. Такую пропажу в интерфейсе ищут долго.
У вывода типа из умолчания есть обратная сторона: выводит он широко.
Диагностик нет. Из "быстрый" компилятор вывел не литеральный тип, а string — как и для обычной let-переменной в статье про литеральные типы. Умолчание задаёт значение, а не список допустимых значений. Если вариантов должно быть ровно два, тип нужно назвать.
Аннотация и умолчание уживаются: level: Level = "info" читается как «тип вот такой, а если не передали — вот это». В тексте диагностики параметр назван Level | undefined: вариант «не передали» компилятор держит в уме и здесь, просто внутрь функции он не проходит.
И ещё одно свойство, общее с JavaScript: умолчание — это выражение. Оно вычисляется при каждом вызове и видит параметры, объявленные левее.
Вопросительный знак и умолчание вместе не ставят: это TS1015, «Параметр не может содержать вопросительный знак и инициализатор». Умолчание и так делает параметр необязательным.
Вывод типа из умолчания подводит там, где само умолчание ничего не значит.
«Аргумент типа "*" нельзя назначить параметру типа null | undefined» — из null компилятору выводить нечего, и тип параметра получился ровно null. Функция, в которую нельзя передать ничего осмысленного, — не то, что задумывали. Лечится аннотацией filler: string | null = null или нормальным умолчанием вроде filler = " ".
Аргументы позиционные: второй аргумент попадает во второй параметр, и пропустить дырку посередине нельзя. Отсюда правило, которое компилятор проверяет отдельно от типов.
TS1016, «Обязательный параметр не должен следовать за необязательным», и заметьте — диагностика на первой строке, а не на вызове. Запрещено само объявление. Причина видна, если попробовать таким объявлением воспользоваться: раз title можно пропустить, вызов order("Рико") должен что-то значить. Но единственный аргумент по позиции попадёт в title, а обязательный name останется пустым. Правило снимает неоднозначность до того, как она появится.
Пропустить необязательный параметр в середине всё-таки можно — передав undefined явно. Это не обход правила, а его прямое следствие: позиция занята, значения нет.
А теперь то, что ломает интуицию: точно такое же по смыслу объявление, но со значением по умолчанию, компилятор принимает без единого слова.
TS1016 не сработал — и это не поблажка. Сигнатура из последней строки объясняет всё: (title: string | undefined, name: string) => string, без всякого знака вопроса. Параметр с умолчанием, стоящий перед обязательным, перестаёт быть пропускаемым: аргумент передавать обязательно, просто разрешено передать undefined. Правило про порядок не нарушено, потому что необязательных параметров здесь попросту нет.
Третий вызов подтверждает это дважды. Компилятор требует два аргумента — «Ожидалось аргументов: 2, получено: 1». Исполнение печатает Рико undefined: строка «Рико» ушла в title, а name не досталось ничего. Вывод практический — значение по умолчанию ставьте в конец списка, иначе оно приносит не удобство, а обязанность писать undefined при каждом вызове.
Проверьте себя на чужом коде: если у параметра есть умолчание, но справа от него стоит обязательный — умолчание почти наверняка мёртвое.
Когда необязательных параметров становится больше двух, позиции перестают работать вовсе: вызов вида find("привет", undefined, undefined, true) прочитать невозможно. Тогда их собирают в один объект с необязательными полями — теми самыми, из статьи про типы объектов.
Порядок полей теперь не важен, на месте вызова видно имя каждого значения, а опечатку ловит проверка лишних свойств из статьи про структурную типизацию: «exact не существует в типе...». Цена — знак вопроса у самого options и ?. при чтении полей.
Обратная задача: аргументов может быть сколько угодно. Три точки перед именем параметра собирают весь хвост в массив — приём из части I, а типизируется он ровно как массив.
Тип number[] описывает не один аргумент, а весь хвост: каждый аргумент должен быть числом, а сколько их — неважно, ноль тоже подходит. Третий вызов напечатал 12, и это не опечатка: reduce сложил ноль с единицей, получил 1, встретил строку — и сложение стало склейкой. То самое преобразование типов из части I, от которого диагностика и защищает.
Тип rest-параметра обязан быть массивом. Забытые квадратные скобки компилятор ловит отдельным кодом.
«Параметр rest должен иметь тип массива» — TS2370. Вторая диагностика, на вызове, — эхо первой, и в ней виден любопытный след: список аргументов компилятор описал кортежем [number, number]. Именно так он его и представляет.
Rest-параметр всегда последний, и это правило не TypeScript, а самого языка.
Диагностик две: TS1014, «Параметр rest должен стоять на последнем месте в списке параметров», и следом обычная путаница со счётом аргументов. А исполнения не будет вообще — движок отказывается разбирать такой код целиком и бросает SyntaxError: Rest parameter must be last formal parameter. Редкий случай, когда компилятор и среда исполнения ругаются на одно и то же: три точки не в конце запрещены самим синтаксисом языка.
На стороне вызова три точки работают в обратную сторону — раскладывают массив в отдельные аргументы. В rest-параметр это проходит всегда, в обычные — не всегда.
«Аргумент расширения должен иметь тип кортежа либо передаваться в параметр rest» — TS2556. У sizes тип number[], массив неизвестной длины, и компилятор не может обещать, что там ровно два числа. У fixed благодаря as const тип readonly [3, 4]: длина известна, вызов разрешён. На исполнении оба варианта дали 12 — но только один из них будет давать его всегда.
В rest-параметре можно написать не массив, а кортеж — тогда получится функция с фиксированным числом аргументов, у которых есть имена.
«Ожидалось аргументов: 2-3, получено: 1» — знак вопроса внутри кортежа работает так же, как у параметра. Имена x, y и label на исполнение не влияют никак, они живут только в типе, зато попадают в подсказку редактора. Приём выглядит экзотикой, но именно на нём держатся типы вроде «те же аргументы, что у той функции», которые ждут в главе про дженерики.
Раз ? добавляет к типу undefined, кажется, что title?: string и title: string | undefined — одно и то же. Внутри функции да. Снаружи нет.
«Ожидалось аргументов: 1, получено: 0» — и это единственная диагностика примера. Функция b обязана получить аргумент, просто этим аргументом разрешено быть undefined. Разница не в типе значения, а в праве промолчать: вызывающий код должен явно сказать «здесь ничего нет», и забыть об этом параметре у него не выйдет.
| Запись | Аргумент можно пропустить | Тип внутри функции |
|---|---|---|
title?: string | да | string | undefined |
title = "гость" | да | string |
title: string | undefined | нет, undefined передают явно | string | undefined |
...titles: string[] | да, хоть ноль аргументов | string[] |
Правая колонка объясняет, почему значение по умолчанию так часто оказывается лучшим выбором: это единственная запись, после которой внутри функции не нужна ни одна проверка. Знак вопроса берут, когда «не передали» и правда означает особый случай, а не просто «подставь обычное».
У функции есть свойство length — сколько аргументов она ждёт. Считает его среда исполнения, а она про типы ничего не знает.
У opt получилось два: знак вопроса стёрся вместе с остальными типами, и до браузера доехала функция с двумя обычными параметрами. А = "x" и ...r — настоящий синтаксис JavaScript, он никуда не девается, и length считает параметры только до него. Хорошее напоминание, что ? живёт исключительно на этапе проверки.
- Знак вопроса разрешает не передавать аргумент и добавляет к типу параметра
undefined: внутри функции его придётся сузить, иначе будет TS18048. - Значение по умолчанию делает параметр необязательным снаружи, но внутри оставляет чистый тип без
undefined— проверять нечего. Явныйundefinedв вызове тоже включает умолчание. - Тип из умолчания выводится широко: из
"быстрый"получаетсяstring, а не литерал. Нужен ограниченный набор значений — пишите аннотацию. - TS1016 запрещает обязательный параметр после необязательного, потому что аргументы позиционные. Параметр с умолчанием под это правило не подпадает — и заодно теряет право быть пропущенным.
- Тип rest-параметра описывает весь хвост, поэтому обязан быть массивом или кортежем (TS2370), а сам параметр — стоять последним (TS1014).
- Раскладывать массив в обычные параметры компилятор не даёт (TS2556): нужна известная длина, то есть кортеж. В rest-параметр раскладывается что угодно.
1/4Объявление function order(title = "гость", name: string) — что скажет компилятор?
2/4В функции function greet(name: string, title?: string) какой тип у title внутри тела?
3/4В function sum(...nums: number) забыли квадратные скобки. Что будет?
4/4В function slot(mode = "быстрый") пройдёт ли вызов slot("бытсрый") с опечаткой?
Скидка, которой может не быть
Функция считает цену со скидкой в процентах. Скидка есть не всегда, поэтому параметр сделали необязательным — и получили диагностику внутри функции и NaN на исполнении. Почините так, чтобы вызов без скидки возвращал полную цену, а сигнатура снаружи не изменилась.
Диагностика TS18048 говорит прямо: discount может оказаться undefined, а арифметика с undefined даёт NaN. Самое короткое лечение — заменить знак вопроса значением по умолчанию.
Ни диагностики, ни NaN: внутри функции discount имеет тип number, а снаружи параметр остался необязательным — вызов price(1000) как работал, так и работает. Вариант со знаком вопроса и const rate = discount ?? 0; даёт тот же результат, но лишней строкой: умолчание делает ровно это, только в объявлении, где его видно вызывающему коду.
Путь из частей
Функция должна склеивать путь из корня и любого числа сегментов. Автор ошибся в типе rest-параметра — и одна опечатка дала четыре диагностики. Исправьте тип и заодно вызов без сегментов: сейчас он возвращает home/ с лишней косой чертой.
Диагностик четыре, но ошибка одна: TS2370 на объявлении. Всё остальное — её последствия. Раз тип параметра объявлен как string, у него нет метода join (TS2339), а списки аргументов обоих вызовов внезапно не подходят под string (два раза TS2345). Чинится первая — исчезают все.
Пустой хвост — законный случай для rest-параметра, поэтому проверку на него пишут почти всегда. Последние две строки — бонус: раскладывать массив в rest-параметр компилятор разрешает без всяких кортежей, длина здесь никого не смущает.
Предскажите три строки
Прежде чем запускать, запишите ответ на два вопроса: сколько будет диагностик и на какой строке, и что напечатают три вызова. Подсказка: одна из строк печатается вопреки здравому смыслу.
Диагностика ровно одна, на шестой строке: «Ожидалось аргументов: 2, получено: 1». Объявление законно — TS1016 сюда не приходит, потому что параметр с умолчанием необязательным не считается. Вывод: id-7, №7, 7undefined.
Третья строка и есть тот самый здравый смысл наоборот: кажется, что единственный аргумент должен попасть в обязательный параметр, а умолчание — подставиться. Но аргументы позиционные, и «7» ушло в prefix. Умолчание перед обязательным параметром бесполезно — переставьте его вправо: function label(code: string, prefix = "№").