Как получить типы аргументов функции в TypeScript (Parameters, infer и условные типы)
Использование встроенного типа Parameters
В TypeScript есть встроенный утилитарный тип Parameters<T>, который извлекает типы параметров функции в виде кортежа. Это самое простое и элегантное решение вашей задачи.
type FetchUserParams = Parameters<typeof fetchUser>;
// type FetchUserParams = [id: number, name: string]
type UpdateSettingsParams = Parameters<typeof updateSettings>;
// type UpdateSettingsParams = [theme: 'light' | 'dark', fontSize: number]
type LogErrorParams = Parameters<typeof logError>;
// type LogErrorParams = [error: Error, source: string, timestamp: number]
Теперь применим это к вашей функции-обёртке:
function wrapFunction<T extends (...args: any[]) => any>(
fn: T,
...args: Parameters<T>
): ReturnType<T> {
console.log('Calling function with args:', args);
return fn(...args);
}
// Примеры использования с полной типовой безопасностью:
wrapFunction(fetchUser, 1, "Alice"); // ✅ OK
wrapFunction(fetchUser, "1", "Alice"); // ❌ Ошибка: 'string' не присваивается 'number'
wrapFunction(fetchUser, 1); // ❌ Ошибка: ожидается 2 аргумента, получен 1
wrapFunction(updateSettings, 'dark', 14); // ✅ OK
wrapFunction(updateSettings, 'blue', 14); // ❌ Ошибка: 'blue' не входит в union тип
wrapFunction(logError, new Error('fail'), 'module.ts', Date.now()); // ✅ OK
Как это работает под капотом?
Тип Parameters<T> внутри использует условный тип с infer:
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
Разберём по шагам:
- Условный тип:
T extends (...args: infer P) => any ? P : never— проверяет, является ли T функцией. - Ключевое слово
infer: создаёт новую переменную типаP, которая будет равна кортежу параметров функции. - Ветвление: если T — функция, возвращаем
P(типы параметров), иначе —never.
Расширенный пример: обработка аргументов перед вызовом
Вы можете не только логировать, но и модифицировать аргументы:
function withLogging<T extends (...args: any[]) => any>(
fn: T
): (...args: Parameters<T>) => ReturnType<T> {
return (...args: Parameters<T>) => {
console.log(`[${new Date().toISOString()}] Calling ${fn.name}`);
const result = fn(...args);
console.log(`[${new Date().toISOString()}] Result:`, result);
return result;
};
}
const loggedFetch = withLogging(fetchUser);
loggedFetch(1, "Alice"); // ✅ Строгая типизация + логирование
Работа с методами классов и контекстом
Если нужно сохранить контекст this:
function bindWithTypes<T extends (this: any, ...args: any[]) => any>(
fn: T,
context: ThisParameterType<T>
): (...args: Parameters<T>) => ReturnType<T> {
return fn.bind(context);
}
class UserService {
constructor(private users: Map<number, string>) {}
getUser(id: number): string | undefined {
return this.users.get(id);
}
}
const service = new UserService(new Map([[1, 'Alice']]));
const boundGetUser = bindWithTypes(service.getUser, service);
boundGetUser(1); // ✅ Тип: (id: number) => string | undefined
Используйте Parameters<T> везде, где нужно получить типы аргументов функции. Это стандартный, хорошо документированный и производительный подход.
Альтернативный подход: ручное использование infer в условных типах
Если по какой-то причине вы не хотите использовать встроенный Parameters (например, для образовательных целей или для кастомной обработки), можно реализовать его самостоятельно:
type MyParameters<T> =
T extends (...args: infer P) => any ? P : never;
// То же самое, что и Parameters<T>
type FetchParams = MyParameters<typeof fetchUser>;
// [id: number, name: string]
Когда это может быть полезно?
- Фильтрация параметров: можно извлечь только часть параметров:
type FirstParam<T> =
T extends (first: infer F, ...rest: any[]) => any ? F : never;
type SecondParam<T> =
T extends (first: any, second: infer S, ...rest: any[]) => any ? S : never;
type First = FirstParam<typeof fetchUser>; // number
type Second = SecondParam<typeof fetchUser>; // string
- Трансформация параметров: например, сделать все параметры опциональными:
type OptionalParams<T> =
T extends (...args: infer P) => any
? Partial<P>
: never;
type OptionalFetchParams = OptionalParams<typeof fetchUser>;
// Partial<[id: number, name: string]> = [id?: number, name?: string]
Ограничения ручного подхода
Встроенный Parameters оптимизирован и учитывает все краевые случаи (например, перегруженные функции). Для повседневного использования рекомендую именно его. Ручное использование infer оправдано только когда нужно нетривиальное преобразование типов параметров.
Ответов пока нет.
Войдите чтобы оставить ответ