Skip to main content

Параметри типів утиліт у TypeScript

Parameters - утилітний тип, який витягує типи параметрів функції у вигляді кортежу, щоб їх можна було перевикористовувати без повторного написання тієї ж сигнатури.

Теорія

TL;DR

  • Parameters<T> - це як ксерокс для списку аргументів функції: отримуєш точну копію всіх типів, у правильному порядку, зі збереженою опціональністю.
  • Parameters<typeof func> перетворює func(a: string, b: number) на [string, number].
  • Зберігає порядок, маркери опціональності та rest-параметри. Ручні псевдоніми типів цього не роблять.
  • Використовуй при обгортанні функцій або побудові generic-утиліт. Для функцій з одним простим параметром сенсу немає.

Швидкий приклад

typescript
const fetchUser = async (id: string, options?: { cache: boolean }): Promise<User> => { return { id, name: 'Alice' } as User; }; type FetchUserArgs = Parameters<typeof fetchUser>; // [id: string, options?: { cache: boolean } | undefined] const callFetch = async (...args: FetchUserArgs) => { return fetchUser(...args); // точна сигнатура, без дублювання };

Якщо у fetchUser з'явиться новий параметр, FetchUserArgs оновиться автоматично. Жодної ручної синхронізації.

Ключова відмінність

Parameters<T> повертає кортеж (tuple), а не звичайний масив. Це означає, що порядок і опціональність зберігаються: [string, number?] не рівнозначний [number?, string]. При ручному описі типів цю структуру доводиться відновлювати щоразу, коли змінюється вихідна функція. З Parameters<T> тип залишається актуальним сам по собі.

Коли використовувати

  • Обгортання або проксування функції: Parameters<T> для точного збігу сигнатури.
  • Higher-order функції та generic-утиліти: витягуй аргументи, щоб не прописувати типи вручну.
  • Тестові моки: відповідність реальній сигнатурі без копіювання типів.
  • API-обгортки: перевикористання типів параметрів ендпоінту у валідаторах або middleware.
  • Пропускай, якщо функція має один простий параметр або ти працюєш з нефункціональним типом.

Як це обробляє компілятор

Parameters<T> - це conditional type, вбудований у компілятор TypeScript:

typescript
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

TypeScript використовує infer P, щоб захопити кортеж аргументів із сигнатури виклику. Rest-параметри стають варіативними кортежами. Опціональні параметри стають T | undefined у результуючому кортежі. Жодних витрат під час виконання. Це суто перевірка на рівні типів.

Типові помилки

Забуваєш typeof для значення функції:

typescript
function add(a: number, b: number) {} type Wrong = Parameters<add>; // Помилка: 'add' - це значення, не тип type Right = Parameters<typeof add>; // [number, number]

Parameters потребує тип, а не посилання на значення. Завжди використовуй typeof для змінної-функції.

Очікуєш непустий результат від функції без параметрів:

typescript
type Params = Parameters<() => void>; // [] type First = Params[0]; // never

Немає параметрів - порожній кортеж. Звернення до [] за індексом повертає never, що іноді дивує тих, хто очікує помилку компіляції.

Неправильне звернення до rest-параметрів за індексом:

typescript
const merge = (...objs: Record<string, any>[]) => ({}); type MergeArgs = Parameters<typeof merge>; // [objs: Record<string, any>[]] type First = Parameters<typeof merge>[0]; // Record<string, any>[]

Rest-параметри дають кортеж з одного елемента, який обгортає тип масиву. [0] повертає масив, а не окремий елемент. На практиці саме тут розробники найчастіше отримують Record<string, any>[] замість очікуваного Record<string, any>.

Де зустрічається в реальному коді

  • React: Parameters<typeof useEffect>[1] витягує тип масиву залежностей для обгорток кастомних хуків.
  • Express: Parameters<typeof getUserHandler> дозволяє перевикористати сигнатуру middleware без повторного оголошення Request, Response, NextFunction.
  • Zod: Типи параметрів передаються у валідатори без дублювання структури схеми.
  • Тести: Обгортай мок з Parameters<typeof realFn> - тест зламається, якщо сигнатура функції зміниться.

Питання на співбесіді

Q: Що поверне Parameters<() => void>?
A: Порожній кортеж []. Немає параметрів - нема що витягувати.

Q: Як Parameters<T> підтримує опціональні параметри?
A: Опціональні параметри стають T | undefined у кортежі. (a?: string) дає [a?: string], що приймає і string, і undefined.

Q: У чому різниця між Parameters<T> і ручним описом типів?
A: Ручні типи застарівають. Якщо вихідна функція змінюється, копія не оновлюється. Parameters<T> завжди відображає актуальну сигнатуру.

Q: Чи можна реалізувати Parameters<T> вручну?
A: type MyParameters<T> = T extends (...args: infer P) => any ? P : never;. Клауза infer P захоплює кортеж аргументів із сигнатури виклику.

Q: Чи працює це зі стрілковими функціями?
A: Так, через typeof arrowFunc. TypeScript виводить тип з анотації або тіла функції.

Приклади

Обгортання Express-обробника маршруту

typescript
const getUserHandler = (req: Request, res: Response, next: NextFunction): void => { res.json({ user: { id: req.params.id } }); }; type HandlerArgs = Parameters<typeof getUserHandler>; // [Request, Response, NextFunction] const withAuth = (handler: (...args: HandlerArgs) => void) => { return (req: Request, res: Response, next: NextFunction) => { if (req.headers.authorization) { handler(req, res, next); } else { next(); } }; }; const protectedHandler = withAuth(getUserHandler); // Типобезпечно. Зміни сигнатуру getUserHandler - TypeScript одразу повідомить про невідповідність.

withAuth приймає будь-який обробник, що відповідає сигнатурі getUserHandler. Ніякого ручного дублювання типів. Змінюєш параметри - помилка з'являється одразу.

Передача аргументів у попередньо налаштовану утиліту

typescript
const formatDate = ( date: Date, locale: string, options?: Intl.DateTimeFormatOptions ): string => { return new Intl.DateTimeFormat(locale, options).format(date); }; type FormatArgs = Parameters<typeof formatDate>; // [date: Date, locale: string, options?: Intl.DateTimeFormatOptions] const formatUS = (...args: FormatArgs): string => { const [date, , options] = args; return formatDate(date, 'en-US', options); }; formatUS(new Date(), 'any-string', { month: 'long' }); // Другий аргумент все ще типізований як string, навіть якщо formatUS його ігнорує

Кортеж зберігає кожну позицію. formatUS очікує string на другій позиції, бо так сказано у FormatArgs, навіть якщо функція цей аргумент не використовує.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?