Объявить произвольно вложенный массив (определение рекурсивного типа)

Скажем, у меня есть функция вроде:

const nested = function(v: string | Array<string> | Array<Array<string>>){...}

проблема в том, что v может быть вложенным на 5 или 6 уровней. Как я могу объявить произвольно вложенный тип?

Например, как я могу справиться:

nested([['zam'],[[[['zimm']]]]])

person Community    schedule 06.12.2018    source источник
comment
связанная проблема на Github: github.com/Microsoft/TypeScript/issues/3496   -  person    schedule 06.12.2018


Ответы (2)


Вы можете довольно легко описать произвольно вложенный тип массива, например:

interface NestedArray<T> extends Array<T | NestedArray<T>> { }

Этот тип рекурсивной ссылки разрешен (где псевдоним type не разрешен), поскольку оценка базового типа интерфейса отложено.

К сожалению, работать с ним непросто. Вы можете создавать значения такого типа:

// works as expected
const nums: NestedArray<number> = [1,[2,[3,[4,[5],6,[7]],[8]],[[9]]]];

// errors as expected
const oops: NestedArray<number> = [1,[2,["3",[4,[5],6,[7]],[8]],[[9]]]]; // error

но компилятор отказывается от проверки таких вложенных типов примерно после пяти уровней в глубину:

// no error!  
const what: NestedArray<number> = [[[[["a"]]]]]; // ????

Кроме того, вы не можете легко вывести окончательный тип элемента, учитывая тип массива. Например:

declare function doesntWork<T>(arr: NestedArray<T>): T;
const t = doesntWork([[1,2,3]]) ; // T is number[] | ConcatArray<number[]>; ????

Вы можете ожидать, что T будет выведено как number, но компилятор не обязан этого делать, поскольку [[1,2,3]] является и NestedArray<number[]>, и NestedArray<number>. Даже если вы попытаетесь заставить NestedArray<T> принимать только T, который не является массивом, компилятор не выведет тип элемента так, как вы хотите.

Если вам нужно определить тип вложенного элемента, вам может понадобиться создать рекурсивный псевдоним type, вероятно, включающий условные типы. Но вы не можете сделать это в TypeScript (по крайней мере, начиная с 3.2).

type NestedElementType<T> = T extends Array<infer A> ? NestedElementType<A> : T; // error ????

Лучшее, что вы можете сделать, это выбрать глубину для поддержки (скажем, 10 уровней), а затем развернуть псевдоним рекурсивного типа:

type NestedElementType<T> = T extends Array<infer A> ? NET1<A> : T;
type NET1<T> = T extends Array<infer A> ? NET2<A> : T;
type NET2<T> = T extends Array<infer A> ? NET3<A> : T;
type NET3<T> = T extends Array<infer A> ? NET4<A> : T;
type NET4<T> = T extends Array<infer A> ? NET5<A> : T;
type NET5<T> = T extends Array<infer A> ? NET6<A> : T;
type NET6<T> = T extends Array<infer A> ? NET7<A> : T;
type NET7<T> = T extends Array<infer A> ? NET8<A> : T;
type NET8<T> = T extends Array<infer A> ? NET9<A> : T;
type NET9<T> = T extends Array<infer A> ? NETX<A> : T;
type NETX<T> = T extends Array<infer A> ? unknown : T; // bail out

Это будет работать:

declare function doesWork<N extends NestedArray<any>>(arr: N): NestedElementType<N>;
const w = doesWork([[1,2,[3],[[4]]]]) ; // returns number ????

Учитывая все эти предостережения, вы можете использовать этот тип:

function flatten<N extends NestedArray<any>>(arr: N): Array<NestedElementType<N>> {
  const ret: Array<NestedElementType<N>> = [];
  arr.forEach(l => {
    if (Array.isArray(l)) {
      ret.push(...flatten(l));
    } else {
      ret.push(l);
    }
  });
  return ret;
}

const flattened = flatten([["a"], ["b"], [[[[["c"]]], "d"]]]); // string[]

Это зависит от вас, если это того стоит. Надеюсь, это поможет; удачи!

person jcalz    schedule 10.12.2018

таким образом работают:

    type NestedArray<T> = T | NestedArray<T>[];
    const arr: NestedArray<string> = [[[["zimm"]]]];
person Kyi Moe Min    schedule 17.07.2020