Интервью-вопрос
Что такое Function Overload в TypeScript
Function Overload описывает несколько вариантов вызова одной функции и помогает вернуть точный тип результата для каждого варианта. Главный риск в ответе: перепутать его с несколькими реализациями или использовать там, где хватит union type.
- Добавлен
- Редакция
Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.
Мини-квиз
Проверка перед разбором
Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.
Вопрос 1 из 50 правильно
Разбор
Разобраться, а не зазубрить
Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.
Базовая идея
Перегрузка функции в TypeScript состоит из двух частей: нескольких overload-сигнатур и одной реализации. Overload-сигнатуры описывают, как функцию можно вызвать снаружи. Реализация содержит реальный код и должна обработать все объявленные варианты.
Простой пример:
function parseValue(value: string): string[];
function parseValue(value: number): number;
function parseValue(value: string | number): string[] | number {
if (typeof value === "string") {
return value.split(",");
}
return value * 100;
}
const tags = parseValue("react,typescript"); // string[]
const percent = parseValue(0.42); // numberЗдесь вызывающий код получает разные типы результата. Для строки это string[], для числа это number. В этом практическая ценность overload. Вы не просто разрешаете разные типы входа, а сохраняете точный контракт между входом и выходом.
Как сказать коротко на интервью
Хороший короткий ответ может звучать так:
Function Overload это несколько сигнатур вызова для одной функции. TypeScript использует их, чтобы понять, какие аргументы разрешены и какой тип вернется для конкретного вызова. При этом реализация одна, и она должна сама проверить аргументы и обработать все варианты.
После этого добавьте практический критерий. Перегрузки нужны не всегда. Если возвращаемый тип не зависит от формы входа, union type часто проще. Если зависит, overload делает API удобнее и уменьшает лишние проверки в вызывающем коде.
Когда выбирать overload, union или generic
На интервью важно не только знать синтаксис, но и показать, что вы умеете выбирать форму API. Перегрузки могут улучшить developer experience. Но если использовать их без необходимости, простая функция быстро превращается в длинный список сигнатур.
Как выбрать подход
Обычно достаточно union type в параметре.Рассмотрите overload, чтобы вызывающий код получил точный тип.Часто лучше подходит generic, а не список перегрузок.Лучше пересмотреть API. Например, принять объект с явными полями.Пример, где overload не обязателен:
function formatValue(value: string | number): string {
return String(value);
}Результат всегда string, поэтому отдельные сигнатуры для string и number почти ничего не дают. А вот если разные входы дают разные выходы, перегрузки помогают избежать ручных проверок после вызова.
Главная ловушка: реализация не является публичной сигнатурой
Внешний код может вызвать функцию только так, как описано в overload-сигнатурах. Сигнатура реализации нужна для тела функции и проверки совместимости.
Неверный вывод выглядит так: если в реализации написан широкий параметр, значит функцию можно вызвать с любым значением.
function loadUser(id: number): Promise<User>;
function loadUser(slug: string): Promise<User>;
function loadUser(value: number | string | boolean): Promise<User> {
return fetch(`/api/users/${String(value)}`).then((response) => response.json());
}
loadUser(true); // ошибка типов, потому что такой overload не объявленВ этом примере широкая реализация опасна не из-за самого overload, а из-за тела функции. Если значение придет из JavaScript-кода, формы или URL без проверки, можно отправить лишний запрос на /api/users/true, получить неверный error или показать в UI пустое состояние вместо понятной ошибки.
Безопаснее держать реализацию настолько широкой, насколько нужно для объявленных перегрузок, и явно проверять аргументы перед сетевым вызовом:
function loadUser(id: number): Promise<User>;
function loadUser(slug: string): Promise<User>;
function loadUser(value: number | string): Promise<User> {
const path = typeof value === "number" ? `id/${value}` : `slug/${encodeURIComponent(value)}`;
return fetch(`/api/users/${path}`).then(async (response) => {
if (!response.ok) {
throw new Error("User request failed");
}
const data: unknown = await response.json();
return parseUser(data);
});
}Здесь parseUser означает вашу проверку формы ответа. Не доверяйте JSON только потому, что overload красиво описал вызов. Не превращайте реализацию в any без причины. И обязательно проверяйте аргументы в теле, потому что в runtime перегрузок уже нет.
Порядок перегрузок
TypeScript проверяет overload-сигнатуры сверху вниз. Поэтому сначала пишите более специфичные варианты, а потом более общие.
function getSetting(key: "theme"): "light" | "dark";
function getSetting(key: string): string;
function getSetting(key: string): string {
return localStorage.getItem(key) ?? "";
}
const theme = getSetting("theme"); // "light" | "dark"Если поставить общий вариант key: string выше, точный результат для "theme" может потеряться. На практике это бьет по автодополнению, условиям в UI и проверкам, которые зависят от конкретного режима.
Практический вывод для frontend-кода
Во frontend overload часто полезен для функций-оберток над API, парсеров, helpers для роутинга и функций с несколькими режимами. Например, один режим возвращает одиночную сущность, другой список.
type User = { id: string; name: string };
function selectUsers(users: User[], mode: "one"): User | undefined;
function selectUsers(users: User[], mode: "many"): User[];
function selectUsers(users: User[], mode: "one" | "many") {
if (mode === "one") {
return users[0];
}
return users;
}
const firstUser = selectUsers(users, "one");
const allUsers = selectUsers(users, "many");Такой API помогает компоненту не угадывать тип результата. Для режима "one" UI сразу видит, что результата может не быть, и может показать empty state. Для режима "many" компонент получает массив и не пишет лишние проверки. Но если режимов становится много, лучше подумать об объекте настроек или разделить функцию. Длинный список перегрузок может быть признаком слишком сложного helper-а.
Как построить сильный ответ
- 1Назвать несколько публичных сигнатур
- 2Сказать, что реализация одна
- 3Пояснить связь аргументов и результата
- 4Упомянуть порядок и проверку в теле функции
- 1Сказать, что TypeScript создает несколько функций
- 2Поставить <code>any</code> и не проверять типы
- 3Не объяснить отличие от union
- 4Забыть, что широкая перегрузка может скрыть точную
Сильный ответ не обязан быть длинным. Достаточно объяснить контракт, привести маленький пример и сказать, где перегрузки уместны. Если вы добавите отличие от union и упомянете, что реализация одна, ответ будет выглядеть уверенно.
Частые ошибки
Где обычно ошибаются
Проверьте формулировки, которые звучат уверенно, но на интервью быстро выдают пробелы.
- 1
Путать перегрузку с несколькими реализациями
В TypeScript у функции остается одна реализация. Если на интервью сказать, что для каждой сигнатуры создается отдельное тело, это будет звучать как перенос опыта из Java или C#. Лучше формулировать так: перегрузки описывают внешний контракт, а внутри вы сами обрабатываете все варианты. - 2
Использовать overload вместо простого union
Если функция всегда возвращает один и тот же тип, перегрузки могут только раздуть код. Например, для форматированияstring | numberв строку часто достаточно одной сигнатуры. Добавляйте перегрузки, когда они дают вызывающему коду более точный тип результата. - 3
Делать реализацию слишком слабой через any
anyв реализации отключает полезные проверки внутри тела функции. Так легко случайно вызвать метод не того типа и получить runtime-баг. Во frontend это может уронить компонент после ответа API или показать неправильный empty state. Лучше использоватьunknown, union или аккуратные проверкиtypeof, если это возможно. - 4
Ставить широкую перегрузку выше точной
TypeScript выбирает подходящую сигнатуру сверху вниз. Если широкая сигнатура стоит первой, она может забрать вызов, для которого ниже есть более точный вариант. В итоге автодополнение и тип результата станут хуже. - 5
Забывать про runtime-проверки
Перегрузки существуют только на этапе проверки типов. В браузере останется обычная JavaScript-функция, поэтому тело должно проверять аргументы и безопасно обрабатывать неожиданные значения. Иначе типы будут выглядеть аккуратно, а ошибка появится у пользователя.
Follow-up
Что могут спросить дальше
Короткие ответы на вопросы, которыми проверяют понимание перегрузок, union types и сигнатуры реализации.
Живые ответы
Видео с похожим вопросом
Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.
Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.
Что такое декоратор в TypeScript 😎
Декоратор в TypeScript это синтаксис с @, который позволяет добавить поведение или метаданные к классу, методу, полю или аксессору. На странице разбираем, чем отличаются современные и legacy-декораторы, где они полезны и какие обещания опасно давать на интервью.
Что такое instanceof в TypeScript 😎
instanceof проверяет объект во время выполнения по цепочке прототипов и помогает TypeScript сузить тип в ветке условия. Разбираем, где он уместен, где не работает и чем заменить его для интерфейсов, примитивов и данных из API.