Array는 다른 언어에서도 많이 나오는 개념이고 당연히 자바스크립트에도 존재하기 때문에 이해하기 어렵지 않을 것이다. 타입스크립트에는 이런 배열 형태를 조금 더 특수하게 사용할 수 있는 tuple이라는 타입이 있다. 자바스크립트에서는 지원되지 않는 데이터 타입이다.
파이썬에도 튜플이 있기는 하다. 요솟값을 생성, 수정, 삭제를 할 수 있는 리스트와는 다르게 튜플은 요솟값을 바꿀 수 없다. 이 파이썬의 튜플과 타입스크립트의 튜플이 같다고 할 수는 없지만 비슷한 점은 존재한다. 타입스크렙트의 튜플에서는 타입에 맞게 요솟값을 수정을 할 수는 있지만 마찬가지로 생성이나 삭제는 불가능하다(라고 생각했는데...). 이후에 설명하겠지만 튜플은 타입의 순서도 정해져 있고 고정된 크기를 가지고 있기 때문이다.
타입스크립트에서 배열을 생성하는 방법은 간단하다.
let numbers: number[] = [1, 2, 3, 4, 5];
let fruits: string[] = ['apple', 'banana', 'strawberry'];
기존 타입을 지정했던 방식과 동일하게 타입을 선언하되, 그 뒤에 배열을 생성하는 대괄호를 추가해주면 된다. 일단은 기본적으로 하나의 타입으로만 배열을 생성하였지만, 지난번에 배웠던 유니온 타입을 이용해서 여러 타입을 선언할 수도 있다.
let mixedArray: (string | number)[] = ['apple', 3, 4, 'banana'];
만약 배열을 순회하며 타입에 따라 다른 동작을 수행하고 싶게 하고 싶다면 타입가드를 사용하자.
Array는 동적인 크기를 가지고 있기 때문에 삽입, 수정, 삭제 등이 자유롭다. 아래 예시 사진을 한 번 보자.
위처럼 배열의 정렬이나 삽입, 수정 등의 메서드를 자유롭게 사용할 수 있다.
튜플은 동적인 크기를 가지는 배열과는 다르게 고정된 크기를 가진다. 또한 서로 다른 타입을 가질 수 있다. 사용 방법은 아래와 같다.
let tuple: [string, number] = ['apple', 3];
let tuple2: [number, string, boolean] = ['apple', 3, true]; // Error
고정된 크기를 가지고 있고, 타입의 순서가 정해져 있기 때문에 해당하는 순서에 맞지 않는 값이 오거나 다른 크기의 요소가 할당될 수 없다.
그래서 당연히 요솟값을 직접적으로 삽입, 삭제하는 push나 pop 등이 오지 못할 거라고 생각을 했는데...
당연히 되지 않을 거라고 생각하고 코드를 작성한 후 실행을 했다. 근데 내 예상과는 전혀 다른 결과가 나왔다.
...? 왜 되지? 튜플은 고정된 크기를 가지고 있고 마찬가지로 타입의 순서 또한 정해져 있는데 그렇다면 push나 pop 등 요솟값에 직접적인 영향을 주는 메서드를 사용할 수 없어야 하는 것 아닌가?
실제 바나나를 기존 튜플에 추가해보면 위와 같은 에러가 뜬다.
왜 이러는 거지...? 예상과 다른 결과에 나는 당황해서 열심히 구글링을 하기 시작했다. 그리고 아니나 다를까, 나와 비슷한 생각을 한 사람들이 이미 스택오버플로에 질문글을 많이 올려두셨다.
[스택오버플로] Typescript array push method can't catch a tuple type of the array
나와 아주 똑같은 생각을 하신 분이다. 1번은 작동을 안 하는데 왜 2번은 작동이 되는가? 이런 비슷한 질문이 상당히 많이 올라와 있었다.
이게 답변인데... 그냥 요약하자면 이미 논의가 된 문제이지만 마이크로소프트에서 거부함 이거다. 기존 코드에 변화가 너무 크게 생길 것 같아서라고 한다.
[Github Issue] Remove destructive methods in tuple type
실제로 깃허브 이슈에 이런 제안을 한 사람이 있다. push, pop, shift 같은 건 제한해야 하는 것 아닌가요? 답변을 봐도 그냥 다른 방법을 제안할 뿐 튜플은 그냥 배열이다 우리 설계는 그렇기 때문에 바꾸지 않을 거다 뭐 이런 내용이다.
이에 따른 제안이 깃허브 이슈에 오픈되어 있는데
type StrictTuple<T extends any[]> =
Omit<T, keyof any[]> extends infer O ? { [K in keyof O]: O[K] } : never;
보다 엄격한 관리를 위해 특정 조건을 만족하는 튜플 타입을 제안하고 있다. 실제 코드를 작성해서 확인해보았다.
드디어 원하는 결과가 반환되고 있긴 하다. 그렇긴 한데... 보니까 실제 배열로 작동하는 것이 아니라 key-value 쌍의 객체 타입을 만드니까 push를 사용하지 못하는 것 같다. 그래서 스택오버플로에 답변을 다신 분도 근데 오히려 이렇게 하는 게 더 번거로울 수도 있으니 그냥 튜플 변형하지 말고 사용하세요~ 라고 하고 계신다. 보니까 이건 최근까지도 논의되고 있는 부분인 것 같아 뭐랄까 속시원히 결론이 나지 않았다.
우선 Array와 Tuple의 차이점에 대해 마지막으로 정리해보자.