Как вы связываете выводимые параметры с типами, используемыми для вызова функции TypeScript

#typescript

Вопрос:

TL;DR — Как мне удалить ошибку в этой ссылке на игровую площадку, не прибегая к @ts-expect-error ней ?

Представьте, что мы определяем удаленный API…

 const api = {
  str2num(_arg: string) { return 42; },
  num2str(_arg: number) { return 'forty-two'; },
};

type API = typeof api;
type APIFuncName = keyof API;
type Func<FN extends APIFuncName> = API[FN];

export function exec<FN extends APIFuncName>(
  name: FN,
  ...args: Parameters<Func<FN>>
) {
  // Unexpected error:
  // "A spread argument must either have a tuple type or be passed to a rest parameter."
  return api[name](...args);
}

// This is fine
console.log(exec('str2num', 'zero'));
console.log(exec('num2str', 0));

// An error as expected: Type 'number' is not assignable to type 'string'
// console.log(exec('str2num', 0));
 

Мы можем использовать эту exec(...) функцию просто отлично, однако TypeScript неожиданно жалуется на реализацию exec(...) .

Я думаю, проблема в том, что TypeScript не может связать тип args с параметрами api[name] .

Сообщение об ошибке несколько сбивает с толку («Аргумент spread должен либо иметь тип кортежа, либо быть передан параметру rest.»), поскольку args явно уже имеет тип кортежа.

Как я могу ввести это правильно, не прибегая к @ts-expect-error?

Ответ №1:

Я нашел решение этой проблемы, но это немного похоже на взлом; в принципе, вы можете написать вспомогательную функцию для выполнения фактического вызова функции, где функция вводится для принятия аргументов в качестве параметра rest. Это удовлетворяет части сообщения об ошибке «… или будет передано параметру rest». Я не уверен, есть ли элегантное решение.

 export function exec<FN extends APIFuncName>(
  name: FN,
  ...args: Parameters<Func<FN>>
) {
  return callHelper(api[name], args);
}

function callHelper<F extends (...args: any[]) => any>(
  f: F,
  args: Parameters<F>
): ReturnType<F> {
  return f(...args);
}
 

Ссылка на игровую площадку

Комментарии:

1. Спасибо! Это менее грязно, чем мое лучшее обходное решение, которое заключалось в switch (name) повторении одного и того же api[name](...args) кода в каждом блоке case. По причинам, которые я не понимаю, это удовлетворяет другую сторону сообщения об ошибке «… Аргумент распространения должен либо иметь тип кортежа». Я бы хотел увидеть исправление, которое не включало бы изменение JavaScript

2. Я подозреваю, что это удовлетворяет части «должен быть тип кортежа», потому что в каждом случае оператора switch у вас есть тип кортежа, а не (эффективно) объединение типов кортежей. Единственное исправление, которое я смог найти, которое не изменило Javascript, — это использование утверждения типа, которое вы можете счесть достаточно безопасным для такой простой функции, как эта.

3. Если у вас есть время — я бы хотел посмотреть, как это исправит утверждение типа. Я не мог его найти.

4. О, конечно, самый простой из них таков (api[name] as Function)(...args) .

5. Ах, я неправильно понял, я думал, что у вас есть способ использовать утверждения типа, чтобы соединить 2 части. Тем не менее, это лучше, чем @ts-ожидать-ошибки. Спасибо за помощь!