Как выводится T
В прошлом уроке TypeScript сам понял, что first([100, 250]) возвращает number | undefined, хотя никто не писал first<number>. Откуда он берёт тип для T?
Вернёмся в порт. Этикетку в рамке на двери контейнера пишет грузчик: он видит, что кладут внутрь, и заполняет рамку. При вызове обобщённой функции грузчиком работает TypeScript. Он смотрит на аргументы и сам решает, какой тип подставить вместо T. Каждый вызов — новая погрузка с новой этикеткой.
Тип из аргументов
Когда вы вызываете first([100, 250]), TypeScript сопоставляет аргумент с параметром:
- Параметр объявлен как
xs: T[]— массив элементов типаT. - Аргумент
[100, 250]— массив чисел,number[]. - Чтобы
number[]совпал сT[], на местеTдолжен стоятьnumber. Это и есть вывод аргумента типа: TypeScript находитTпо типам аргументов.
Вывод идёт заново при каждом вызове. Прошлый вызов не влияет на следующий: если затем вызвать first(["Paris", "Rome"]), T станет string, а у первого вызова останется number. Поэтому одна функция возвращает точный тип для разных входов.
Если в массиве значения разных типов, T получает их объединение. У литерала [1, "a"] тип (string | number)[], значит T — это string | number.
Пройдите программу по шагам. Следите за подсказкой у переменных с результатом: она показывает, каким стал T в этом вызове.
1function first<T>(xs: T[]): T | undefined {подсказка редактораxs: T[]2 return xs[0];3}4 5const price = first([100, 250]);6const city = first(["Paris", "Rome"]);7const mixed = first([1, "a"]);8const exact = first<number>([1, "2"]);9 10function createList<T>(): T[] {11 return [];12}13 14const items = createList();15const nums = createList<number>();
Внутри объявления T — пустая рамка: какой там будет тип, решит вызов.
Два параметра типа
Параметров типа может быть несколько. Каждый из них TypeScript выводит из своего аргумента, независимо от остальных:
function pair<A, B>(a: A, b: B): [A, B] {
return [a, b];
}
const entry = pair("Ann", 30);
console.log(entry[0], entry[1]); // Ann 30Разберём, что здесь происходит:
<A, B>объявляет два параметра типа через запятую.- Из
"Ann"TypeScript выводитA—string, из30выводитB—number. - Результат объявлен как кортеж
[A, B], поэтому уentryтип[string, number]. console.logпечатаетAnn 30.
Когда выводить не из чего
У функции createList из примера выше нет параметров, и T нигде среди аргументов не встречается. Если результат кладут в переменную без аннотации, рамку заполнить нечем, и TypeScript ставит в неё unknown:
function createList<T>(): T[] {
return [];
}
const items = createList();
const nums = createList<number>();
nums.push(5);
console.log(items.length, nums.length); // 0 1У items тип unknown[]. В такой массив можно положить что угодно, а вот воспользоваться элементом без проверки нельзя. Для nums аргумент типа написан явно, и это обычный number[]: в него кладут только числа. Тот же тип даст и аннотация переменной: в const ids: number[] = createList() TypeScript берёт T из того, куда идёт результат.
Здесь аналогия с грузчиком заканчивается. Настоящий грузчик заглянет в пустой контейнер и спросит, что в него положат потом. TypeScript решает всё в момент вызова: строки после него на T не влияют. Если ниже написать items.push(5), у items всё равно останется тип unknown[].
На собеседовании часто спрашивают, как TypeScript выбирает T, если вызвать обобщённую функцию без угловых скобок. Ответ: выводит его из аргументов этого вызова, а если в них T нет — из аннотации переменной, куда идёт результат; без неё T становится unknown.
Частая ошибка
В функцию с одним T на два параметра передали значения разных типов:
function both<T>(a: T, b: T): T[] {
return [a, b];
}
console.log(both(1, "a")); // ✗error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.Кажется, что T станет string | number, как у массива [1, "a"]. Но T — один тип на весь вызов: TypeScript взял number из первого аргумента, и строка под него не подошла. Внутри одного литерала массива TypeScript объединяет типы элементов, а для двух отдельных аргументов одного T — нет. Если функция должна принять оба типа, скажите это явно: both<string | number>(1, "a").
Главное
- TypeScript выводит
Tиз аргументов вызова:first([100, 250])даётnumber | undefined, аfirst(["Paris"])—string | undefined. - Вывод идёт заново при каждом вызове, поэтому одна функция возвращает точный тип для разных входов.
- Несколько параметров типа выводятся независимо:
pair("Ann", 30)даёт[string, number]. - Если
Tвыводить не из чего — ни из аргументов, ни из аннотации переменной, — он становитсяunknown; тогда аргумент типа пишут явно:createList<number>().