Колбэки и контекстная типизация
Во второй теме параметр без типа был ошибкой TS7006: выводу нечего читать. А в JavaScript колбэки пишут без всяких типов, и в TypeScript такой код тоже компилируется:
const prices = [100, 250];
const total = prices.map((p) => p + 300);
console.log(total.join(" ")); // 400 550У p нет аннотации, но и ошибки нет. Наведите курсор на p: подсказка покажет (parameter) p: number. Откуда TypeScript это узнал?
Вспомните табличку автомата. У map тоже есть табличка — её тип. На ней написано, что map сделает с вашей функцией: вызовет её для каждого элемента и передаст ей элемент, его номер и сам массив. Когда стрелка стоит прямо в вызове prices.map(...), TypeScript читает эту табличку и сам подписывает параметры. p — элемент массива чисел, значит number.
Тип из места вызова
Контекстная типизация — так называют вывод типа из места, где стоит функция. Обычный вывод идёт от значения к имени: из let count = 5 получается number. Контекстный идёт в обратную сторону — от места к функции:
- стрелка передана в
mapмассиваnumber[]— её параметр получаетnumber; - стрелку кладут в переменную с типом функции — параметры берут типы из этого типа;
- стрелка передана в функцию с параметром-функцией — типы приходят из сигнатуры этой функции.
Поэтому колбэкам, которые пишут прямо в вызове, аннотации обычно не нужны. Тип результата при этом выводится как всегда — из тела: p + 300 даёт number, и total получает тип number[].
Нажимайте «Следующий шаг» и следите, откуда каждая стрелка берёт тип параметра — и где ей взять его неоткуда.
1const prices = [100, 250];подсказка редактораprices: number[]2const total = prices.map((p) => p + 300);3const addFee = (p) => p + 300;4const addFeeTyped = (p: number) => p + 300;5type Visit = (x: number, i: number) => void;6const log: Visit = (x) => {7 console.log(x);8};9prices.forEach(log);
TypeScript вывел из литерала массив чисел.
Меньше параметров — можно
map передаёт колбэку три аргумента, а стрелка (p) => p + 300 принимает один. Функция с меньшим числом параметров подходит к типу функции с большим: лишние аргументы она не читает, и JavaScript это разрешает. Так работают почти все колбэки: номер элемента и сам массив нужны редко.
Наоборот нельзя. Функция, которой нужно больше параметров, чем передаст вызывающий код, не подходит:
type Visit = (x: number, i: number) => void;
const both: Visit = (x: number, i: number, extra: number) => { // ✗
console.log(x, i, extra);
};error TS2322: Type '(x: number, i: number, extra: number) => void' is not assignable to type 'Visit'.
Target signature provides too few arguments. Expected 3 or more, but got 2.Вторая строка сообщения объясняет причину: «тип, к которому присваивают, даёт слишком мало аргументов — нужно 3 или больше, а есть 2». Тот, кто вызывает Visit, передаст только x и i, и в extra всегда был бы undefined.
На собеседовании любят спрашивать, почему колбэк с меньшим числом параметров подходит к типу, где их больше. Ответ: вызывающий код передаст лишние аргументы, а функция имеет право их не читать. Обратное было бы опасно: функция ждала бы аргумент, которого никто не передаст.
Частая ошибка
Колбэк вынесли из вызова в константу, чтобы дать ему имя:
const prices = [100, 250];
const addFee = (p) => p + 300; // ✗
console.log(prices.map(addFee).join(" "));error TS7006: Parameter 'p' implicitly has an 'any' type.Здесь аналогия с табличкой заканчивается. Человек может прочитать табличку заранее и запомнить, а TypeScript читает табличку map, только когда стрелка стоит прямо в вызове map. То, что addFee строкой ниже передаётся в map, тип p не подсказывает. Вынося колбэк, пишите тип параметра сами — или дайте константе тип функции, как const log: Visit выше:
const prices = [100, 250];
const addFee = (p: number) => p + 300;
console.log(prices.map(addFee).join(" ")); // 400 550Главное
- Контекстная типизация берёт типы параметров из места, где стоит функция: из вызова
map, из типа переменной, из сигнатуры функции, которой её передают. - Стрелке прямо в вызове аннотации не нужны, а вынесенная в константу стрелка без типов даёт ошибку
TS7006. - Функция с меньшим числом параметров подходит к типу функции с большим; с большим — не подходит.
- Колбэк, который выносят из вызова, получает типы параметров явно:
(p: number) => p + 300.