Typescript 3.0.1 не может понять вложенные сокращения - PullRequest
0 голосов
/ 28 августа 2018

Итак, у меня есть этот код, который находит все возможные комбинации х под-массивов:

const optionGroups: string[][] = [['Beer', 'Not beer'], ['1', '2', '11']];

const combinations = optionGroups.reduce((opt1, opt2) => {
  return opt1.reduce((acc, option1) => {
    return acc.concat(opt2.map(option2 => {
      return (<string[]>[]).concat(option1, option2);
    }));
  }, []);
});

return combinations; // <-- This is not a string[]

Однако Typescript считает тип возвращаемого значения string[], тогда как на самом деле это string[][].

Выход [['1', 'Beer'], ['2', 'Beer']...] и т. Д.

Я не могу на всю жизнь понять, как исправить тип. Вы можете попробовать фрагмент самостоятельно и посмотреть.

Даже приведение дает ошибку:

Тип 'string []' нельзя преобразовать в тип 'string [] []'. Тип 'string' не сопоставим с типом 'string []'.

Есть указатели?

Ответы [ 2 ]

0 голосов
/ 28 августа 2018

Взгляните на определение типа Array.prototype.reduce, которое используется в данном случае (первый вызов к нему):

reduce(callbackfn: (prev: T, curr: T) => T): T;

T - тип элемента массива. Поскольку ваш вызов работает на string[][] или Array<string[]>, T является string[]. Таким образом, возвращаемое значение этого запроса на уменьшение также должно быть string[]. Так что, судя по приведенным здесь типам, все кажется правильным.

Но это, очевидно, не то, что происходит, когда вы запускаете код. Возвращаемое значение является фактическим string[][]. Когда вы отладите свой код, вы увидите, что типы будут фактически меняться во время выполнения.

Причина этого в том, как reduce работает, если не передать начальное значение:

Если начальное значение не указано, будет использоваться первый элемент в массиве.

Таким образом, первое значение для opt1 будет первым элементом optionGroups a string[]. Затем, после запуска внутреннего кода, результат на самом деле равен string[][]. Вы можете легко подтвердить это, сделав типы явными внутри:

const combinations = optionGroups.reduce((opt1, opt2) => {
  const result = opt1.reduce((acc, option1) => {
    return acc.concat(opt2.map(option2 => {
      return (<string[]>[]).concat(option1, option2);
    }));
  }, <string[][]>[]);
  return <any>result;
});

Итак, проблема в том, что result - это string[][], но внешнее уменьшение все равно принимает string[] для предыдущего значения (из-за его статического типа). Таким образом, вам придется использовать другое определение типа для reduce, которое допускает другой тип для предыдущего значения и текущего значения:

reduce<U>(callbackfn: (prev: U, curr: T) => U, initialValue: U): U;

К сожалению, здесь начальное значение является обязательным, поэтому вам придется его передать. Вы не можете передать null или даже пустой массив здесь, так как это нарушит вашу логику, поэтому вам придется немного изменить свою логику, чтобы по-разному обрабатывать первую итерацию. Например, вот так:

const combinations = optionGroups.reduce<string[][]>((opt1, opt2) => {
  if (opt1 === null) {
    return opt2.map(option2 => [option2]);
  }

  return opt1.reduce((acc, option1) => {
    return acc.concat(opt2.map(option2 => {
      return (<string[]>[]).concat(option1, option2);
    }));
  }, <string[][]>[]);
}, null);
0 голосов
/ 28 августа 2018

Использование внешнего сокращения для просто деструктурирования optionGroups на два его элемента странно и является источником проблемы, потому что, когда вы выполняете сокращение без начального состояния, предполагается, что состояние имеет тот же тип, что и элемент ввода, т. е. string[]. Внешний обратный вызов на самом деле возвращает string[][], но несоответствие не обнаруживается, поскольку тип начального состояния [] внутреннего сокращения расширен до any[] без явной ошибки any; см. этот выпуск . Включение strictNullChecks отключит эту плохую форму расширения и даст вам ожидаемую ошибку типа.

...