Как представить вложенный массив с помощью typescript

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

const a = ['foo', ['aa'], [['zzz',['bar']]]];

export const acceptsArray = (v: Array<any>) : string => {
   returns flattenDeep(v).join(' ');
};

помимо использования Array<any>, как я могу представить вложенный массив строк?


person Alexander Mills    schedule 22.11.2018    source источник


Ответы (2)


КОРОТКИЙ ОТВЕТ

Новая функция работает только в Typescript версии 3.7+.

type A = 'foo' | 'aa' | 'zzz' | 'bar' | A[]

const a:A = ['foo', ['aa'], [['zzz',['bar']]]];

export const acceptsArray = (v: Array<A>) : string => {
   returns flattenDeep(v).join(' ');
};

ПОЛНЫЙ ОТВЕТ

Важно отметить, что существует двусмысленность в отношении того, что является намерением исходного авторского кода. Короткий ответ вверху — это один из способов ввода, ниже — другой, оба варианта допустимы. Только Автор может решить, что он на самом деле имел в виду. Беллоу-тип более строгий.

Преимущество типизации заключается в документировании намерений программиста. Но поскольку вопрос автора не имеет типа, я не могу точно сделать вывод, что он имел в виду.

// When autor say this:
const a = ['foo', ['aa'], [['zzz',['bar']]]];

// Should he mean this:
type A0 = 'foo' | 'aa' | 'zzz' | 'bar' | A[]

// or this:
type A1 = string | string[] | (string | string[])[][] 

// Both types 'A0' and 'A1' are valid types for variable 'a', 
// but type 'A1' is strictier.

ИСХОДНЫЙ ОТВЕТ

ПРИМЕЧАНИЕ: я пишу ответ ниже, когда TS 3.5 была последней версией. Необходимое важное обновление упомянуто выше. Ниже ответ все еще действителен. Я переработаю ответ, чтобы сделать его короче, когда это возможно.

Более строгий способ решить вашу проблему:

type A = string | string[] | (string | string[])[][]

const a:A = ['foo', ['aa'], [ ['zzz',['bar'] ] ] ];

export const acceptsArray = (v: Array<A>) : string => {
   returns flattenDeep(v).join(' ');
};

Общий вопрос

Приведенный выше ответ работает, но давайте решим, вероятно, ваш более общий вопрос:

Как представить вложенный массив произвольного уровня любого произвольного типа?

ВКЛАДЫВАНИЕ: Представление

Во-первых, любой массив имеет тип. Вот массив строк:

type A = Array<string> // type definition
const a: A = ['a','b','c'] //instance

Ниже мы пойдем немного дальше и введем внутрь массива еще один массив. В частности, у нас есть массив «массив строк» ​​и ничего более:

type A = Array<string>
type B = Array<A>
// Ok ! 
const b0: B = [['a','b','c'],['foobar'],['1','2','3','4','5']] //ok
const b1: B = [['1','2','3']] //ok

//Attention !!!
const b2: B = ['1','2','3'] //error! Array<string> is not equal to Array<Array<string>>
const b3: B = [[1,2,3]] // error! Array<Array<number>> is not equal to Array<Array<string>>

Тип B выше может быть записан так: type B = Array<Array<string>>. В Typescript, если вам нужен «Массив массива строк», вы также можете представить его следующим образом:

Самое короткое представление

type X0 = Array<Array<string>>
type X1 = string[][]  // short form!

Точно так же, если вам нужен «Массив массива массива строк», вы можете сделать это:

type Y0 = Array<Array<Array<string>>>
type Y1 = string[][][] // short form!

// this also works!!
type Y2 = (string | number)[][][]

и так далее...

ВКЛАДЫВАНИЕ: проверка типов

Важно понимать, что Typescript строго соответствует именно тому, что вы указываете как тип массива. Например:

// Given this types...
type A = string
type B = Array<string>
type Both = A | B

// and this instances...
const a:A = 'foo'
const b:B = ['foo']

// so...

const r0: Array<A> = [a,a] //ok
const r1: Array<B> = [a,a] //error: string is not of type Array<string>
const r2: Array<B> = [b,b] //ok
const r3: Array<Both> = [a,b] //ok


ВКЛАДЫВАНИЕ: универсальность

Что, если нам нужен массив типа T, где T — это любой конкретный тип.

// Some specific types
type SPECIFIC_TYPE_01 = string
type SPECIFIC_TYPE_02 = number
type SPECIFIC_TYPE_03 = //...etc

// A generic array of any specific type
type GenericArray<T> = Array<T>

// use:
const a: GenericArray<SPECIFIC_TYPE_01> = ['a','b','c'] //ok
const b: GenericArray<SPECIFIC_TYPE_02> = [1,2,3] //ok

Примечание. Очевидно, что вместо использования этих длинных имен вы можете использовать только Array<string>, Array<number> и т. д. Я просто хочу показать, как дженерики работают с массивами.


ИТОГИ

Ниже я использую знания выше, чтобы решить вашу первоначальную проблему.

// Given...
type NestedArray0<T> = T[] //same as Array<T>
type NestedArray1<T> = T[][] //same as Array<Array<T>>
type NestedArray2<T> = T[][][] // same as Array<Array<Array<T>>>
type NestedArray3<T> = T[][][][]  // etc you get the point...

// So you can do things like...
const a0: NestedArray0<number> = [1,2,3]  //ok
const a1: NestedArray1<number> = [[1,2,3]]   //ok
const a2: NestedArray2<number> = [[[1,2,3]]]   //ok
const a3: NestedArray3<number> = [[[[1,2,3]]]]  //ok

// The type of your examples (based in what you informed) is...
type MyType = 
    | string  // 'foo'
    | NestedArray0<string> // ['aa']
    | NestedArray1<string | string[]> //  [['zzz',['bar']]]

const a: Array<MyType> = ['foo', ['aa'], [['zzz',['bar']]] ]; //ok

// so you can do

export const acceptsArray = (v: Array<MyType>) : string => {
   returns flattenDeep(v).join(' ');
};

Спасибо.

person Flavio Vilante    schedule 28.06.2019
comment
это идет на два уровня глубже? Я хочу представить произвольный уровень вложенности - person Alexander Mills; 28.06.2019
comment
@AlexanderMills да, это произвольный уровень вложенности. Я отредактировал свой первоначальный ответ, добавив концептуальное справочное описание. Хитрость заключается в том, что Array<Array<T>> отличается от Array< T | Array<T> >, и ваш вопрос, похоже, не делает этого различия. Я надеюсь сделать это различие ясным. Дайте мне знать, если у вас есть дополнительные сомнения. - person Flavio Vilante; 30.06.2019
comment
Основываясь на этом ответе, я думаю, что следующим вопросом будет: есть ли способ разработать тип, который инкапсулирует все эти знания? Я думаю, что есть, но пока не было времени подумать. - person Flavio Vilante; 09.07.2019
comment
В TS 3.7 появилась новая функция, упрощающая решение подобных проблем. Возможно, я отредактирую этот ответ в будущем. См.: github.com/microsoft/TypeScript/pull/33050 - person Flavio Vilante; 20.10.2019

Пожалуйста, проверьте эту служебную функцию, которую я написал ранее.

// NestedArray<T> represents T or Array of T or Array of Array of T .....
// let nestedNumbers: NestedArray<number> = [[[[[1]]]]];
export type NestedArray<T> = T | Array<NestedArray<T>>;

// Able to flatten deeply nested array
// flattenArray(nestedNumbers) should produce => [1] : Array<number>
export const flattenArray = <T>(arr: NestedArray<T>): Array<T> => {
  if (!Array.isArray(arr)) return arr ? [arr] : [];

  return arr.reduce<Array<T>>((acc: Array<T>, item: NestedArray<T>) => {
    if (Array.isArray(item)) {
      return [...acc, ...flattenArray(item)];
    }
    return [...acc, item];
  }, []);
}
person Mikail Çolak    schedule 12.07.2021