Возвращает кортеж точно такой же длины из функции

#typescript

#typescript

Вопрос:

Я пытаюсь вернуть из функции кортеж точно такой же длины, что и кортеж, переданный в качестве параметра.Я думал, что мне удалось сделать это с помощью use generics, но я все еще сталкиваюсь с ошибкой при использовании spread operator для результата.

То, что я пытаюсь сделать, лучше всего объяснить на примере. Я пытаюсь написать некоторую вспомогательную функцию для проверки и извлечения параметров запроса из объекта экспресс-запроса.

Первый пример без типов —

 const getQueryParams = (req: Request) => (keys) => {
  const values = keys.map(key => {
    const value = req.query[key];
    if (value !== undefined amp;amp; value !== null amp;amp; typeof value === 'string') {
      return value;
    }
    throw new Error(`Missing query param ${key}`);
  });
  return values;
}
  

Я добавил несколько вспомогательных типов, чтобы попытаться сказать — то, что возвращает эта функция, имеет точно такую же длину, какую она передала:

 type ArrLength<T extends readonly any[]> = T['length'];
type SameLength<T extends readonly any[]> = readonly string[] amp; {length: ArrLength<T>};


const getQueryParams = (req: Request) => <T extends readonly string[]>(keys: T): SameLength<T> => {
  const values = keys.map(key => {
    const value = req.query[key];
    if (value !== undefined amp;amp; value !== null amp;amp; typeof value === 'string') {
      return value;
    }
    throw new Error(`Missing query param ${key}`);
  }) as SameLength<T>;
  return values;
}

  

и, похоже, это работает, по крайней мере частично. Например, это работает:

 let params = getQueryParams(req)(['token', 'user', 'count'] as const);
params = ['1'] as const; //ERROR! notice typescript knows here that parmas is of length 3
params = ['1', '2', '3'] as const; //OK! notice typescript knows here that parmas is of length 3
  

однако попытка вызвать функцию с помощью оператора распространения:

 someApiMethod(...params)
  

Я получаю сообщение об ошибке, Expected 3 arguments, but got 0 or more .

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

У кого-нибудь есть идеи, как я исправляю свои типы / делаю какой-то другой обходной путь?

(В качестве альтернативы, если это неразрешимо, я бы с радостью принял ответ, объясняющий, почему, и соответствующую проблему в репозитории github Typescript, если она существует. Я не могу найти ни одного)

Ответ №1:

Я думаю, вам будет проще, если вы будете использовать сопоставленные типы кортежей, подобные этому:

 type SameLength<T extends readonly any[]> = { [K in keyof T]: string };
  

Это приводит к получению подлинного типа кортежа, когда вы передаете его в:

 type TestTuple = SameLength<[false, 1, "two", Date]>;
// type TestTuple = [string, string, string, string]
  

Обратите внимание, что вам может потребоваться промежуточное утверждение для получения результата map() from string[] to SameLength<T> , например:

 const getQueryParams = (req: Request) => <T extends readonly string[]>(keys: T): SameLength<T> => {
  const values = keys.map(key => {
    const value = req.query[key];
    if (value !== undefined amp;amp; value !== null amp;amp; typeof value === 'string') {
      return value;
    }
    throw new Error(`Missing query param ${key}`);
  }) as readonly string[] as SameLength<T>; // might require intermediate assertion
  return values;
}
  

И теперь это params подлинный кортеж, вы сможете его распространять:

 const handler = async (req: Request, res: any) => {
  let params = getQueryParams(req)(['token', 'user', 'count'] as const);
  const r = await someApiMethod(...params); // okay
  res.json(r);
}
  

Игровая площадка ссылка на код