Menu

TypeScript 배열 타입: 객체 배열, map과 filter

TypeScript에서 배열에 타입을 지정하는 방법: T[]와 Array<T>, 객체 배열, 여러 타입을 담는 배열, readonly 배열, 그리고 map, filter, reduce, find가 반환하는 타입. 리터럴 배열에서 includes를 쓸 때의 함정도 다룹니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

TypeScript의 배열 타입은 요소 타입 뒤에 []를 붙인 것입니다. string[]는 문자열 배열, number[]는 숫자 배열입니다. 제네릭 표기 Array<string>도 같은 타입입니다. 배열에 타입을 지정하면 추가하는 요소와 읽어 오는 요소가 모두 그 타입을 가집니다.

@ts-expect-error 줄은 컴파일 오류(TS2345)입니다. 여기서는 예상된 오류로 표시했으므로 블록이 실행되며, 타입은 지워지기 때문에 런타임에는 42가 실제로 추가됩니다. 출력에서 확인할 수 있습니다.

string[] vs Array<string>

표기같은 타입설명
string[]Array<string>흔히 쓰는 표기.
(string | number)[]Array<string | number>괄호가 필요합니다. string | number[]는 "문자열, 또는 숫자 배열"이라는 뜻입니다.
readonly string[]ReadonlyArray<string>push, pop, sort, 인덱스 대입이 없습니다.
User[]Array<User>객체 배열은 객체의 타입을 씁니다.
string[][]Array<Array<string>>2차원 배열(그리드).

코드베이스마다 한 가지 스타일을 정하세요. typescript-eslint의 array-type 규칙은 기본값이 T[]입니다.

객체 배열

타입 별칭이나 인터페이스로 요소를 기술한 뒤 Type[]를 씁니다. 배열에서 읽어 온 모든 값이 그 형태에 맞는지 검사됩니다.

users에 추가하는 객체 리터럴은 User와 정확히 일치해야 합니다. admin이 빠졌거나 프로퍼티 이름에 오타가 있으면 컴파일 오류입니다.

map, filter, reduce, find의 타입

배열 메서드는 제네릭이므로 결과에 타입이 붙습니다. 각 메서드가 런타임에 하는 일은 JavaScript 배열 메서드 페이지에서 다루고, 여기서는 TypeScript가 더하는 타입을 봅니다.

메서드T[]에서의 결과 타입
map(fn)U[], U는 fn이 반환하는 타입
filter(fn)T[](또는 더 좁은 타입, 아래 참고)
find(fn)T | undefined
findIndex(fn), indexOf(x)number(없으면 -1)
some(fn), every(fn), includes(x)boolean
reduce(fn, init)init의 타입(또는 타입 인수 reduce<R>(...))
at(i)T | undefined
join(sep)string

마지막 예제가 동작하는 이유는 TypeScript(5.5부터)가 (n) => n !== undefined를 타입 서술어(type predicate)로 추론하기 때문입니다. 그래서 filter는 (number | undefined)[]가 아니라 number[]를 반환합니다. 추론할 수 없는 검사라면 서술어를 직접 쓰세요: filter((x): x is User => x !== null).

여러 타입을 담는 배열

유니언 요소 타입은 섞어 담는 것을 허용합니다. 배열 타입의 유니언은 허용하지 않습니다.

[name, age] 쌍처럼 위치마다 타입이 정해져 있다면 튜플을 쓰세요. [string, number]는 인덱스 0이 문자열이고 인덱스 1이 숫자라는 것을 알지만, (string | number)[]는 모릅니다.

readonly 배열

readonly T[]는 배열을 변경하는 모든 메서드를 없앱니다. 함수가 바꾸면 안 되는 매개변수와 상수에 쓰세요.

index.ts(3,12): error TS2339: Property 'push' does not exist on type 'readonly number[]'.

push 줄을 지우면 블록은 4를 출력합니다. 변경 가능한 number[]는 언제나 readonly number[]가 필요한 자리에 넘길 수 있으므로, readonly 매개변수는 호출하는 쪽에 아무 부담도 주지 않습니다. 이 검사는 컴파일 시점에만 이루어지며 런타임에는 평범한 배열입니다. readonly 배열을 정렬하려면 복사본을 정렬하세요: [...values].sort().

리터럴 배열에서 includes의 함정

as const는 배열을 리터럴 타입의 readonly 튜플로 바꿉니다. 허용 값 목록으로 쓰기에 좋지만, 그 includes는 해당 리터럴만 받습니다.

검사를 타입 가드(value is Color)로 감싸면 넓히는 작업은 한 번만 하고, 호출하는 쪽은 좁혀진 값을 돌려받습니다.

인덱싱과 빈 배열

arr[i]를 읽으면 i가 범위를 벗어나 런타임 값이 undefined여도 타입은 T입니다. at(i)의 타입은 T | undefined이고, 컴파일러 옵션 noUncheckedIndexedAccess를 켜면 일반 인덱싱도 T | undefined를 반환합니다.

queue[0].toUpperCase()는 컴파일은 되지만 런타임에 TypeError를 던집니다. 인덱스에 값이 없을 수 있다면 at(), 길이 검사, 또는 noUncheckedIndexedAccess를 쓰세요.

자주 묻는 질문

TypeScript에서 배열 타입은 어떻게 선언하나요?

요소 타입 뒤에 []를 씁니다: let names: string[] = ["a", "b"]. 제네릭 형태인 Array<string>도 완전히 같은 뜻입니다. 객체 배열이라면 객체 타입이나 인터페이스를 요소 타입으로 씁니다: User[].

string[]와 Array<string>의 차이는 무엇인가요?

없습니다. 같은 타입을 두 가지로 표기한 것입니다. string[]가 더 흔하게 쓰입니다. 요소 타입이 복잡할 때는 제네릭 형태가 더 읽기 쉽고, readonly 배열에서도 readonly string[]와 ReadonlyArray<string>은 같습니다.

TypeScript에서 find는 왜 undefined를 반환하나요?

일치하는 요소가 없을 수 있으므로 array.find()는 T | undefined를 반환합니다. strict에서는 결과를 쓰기 전에 if 검사, 옵셔널 체이닝(found?.name), 기본값(found ?? fallback) 등으로 undefined 경우를 처리해야 합니다.

TypeScript에서 여러 타입을 담는 배열은 어떻게 타입을 지정하나요?

괄호로 감싼 유니언 요소 타입을 씁니다: (string | number)[]는 각 요소가 문자열 또는 숫자인 배열입니다. 이는 string[] | number[]와 다릅니다. 후자는 문자열만 담은 배열이거나 숫자만 담은 배열입니다. [string, number]처럼 타입의 순서가 고정되어 있다면 튜플을 쓰세요.

as const 배열에서 includes를 쓰면 왜 오류가 나나요?

["red", "green"] as const 같은 리터럴의 readonly 배열은 includes(searchElement: "red" | "green")를 가지므로, 일반 string을 넘기면 TS2345 오류가 납니다. 검사할 때 배열을 넓히세요: (COLORS as readonly string[]).includes(input). input을 리터럴 유니언으로 좁히는 타입 가드 안에서 하는 것이 가장 좋습니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기