TypeScript·
Tuple과 readonly
- #TypeScript
Tuple과 readonly는 별개의 개념이다
Tuple의 정의는 불변 배열이 아니라, 길이와 Index와 매칭되는 값의 타입이 정해진 타입이다.
즉, readonly 튜플이 있을 수 있고, mutable 튜플이 있을 수 있고, readonly 배열, mutable 배열이 있을 수 있다.
따라서, readonly와 튜플의 여부는 별개이므로 T extends readonly any[]로 튜플과 배열을 구분할 수는 없다.
1// -----------------------------2// 1) mutable tuple (요소 변경 가능)3// -----------------------------4let mt: [number, string] = [1, "a"];56mt[0] = 2; // OK (number)7mt[1] = "b"; // OK (string)8// mt[0] = "x"; // Error: string is not assignable to number91011// -----------------------------12// 2) readonly tuple (요소 변경 불가)13// -----------------------------14let rt: readonly [number, string] = [1, "a"];1516// rt[0] = 2; // Error: Cannot assign to '0' because it is a read-only property17// rt.push("x") // Error: Property 'push' does not exist on type 'readonly [number, string]'181920// -----------------------------21// 3) mutable array (요소 변경 가능)22// -----------------------------23let ma: number[] = [1, 2, 3];2425ma[0] = 10; // OK26ma.push(4); // OK272829// -----------------------------30// 4) readonly array (요소 변경 불가)31// -----------------------------32let ra: readonly number[] = [1, 2, 3];3334// ra[0] = 10; // Error35// ra.push(4); // Error363738// -----------------------------39// 5) readonly 여부와 tuple 여부는 별개 축이다.40// 따라서 `T extends readonly any[]`는41// "readonly 배열/튜플"을 모두 포함하며,42// tuple vs array 구분이 되지 않는다.43// -----------------------------44type IsReadonlyArrayLike<T> = T extends readonly any[] ? true : false;4546type A = IsReadonlyArrayLike<number[]>; // true (mutable array도 readonly any[]에 할당 가능)47type B = IsReadonlyArrayLike<readonly number[]>; // true (readonly array)48type C = IsReadonlyArrayLike<[number, string]>; // true (mutable tuple도 readonly any[]에 할당 가능)49type D = IsReadonlyArrayLike<readonly [number, string]>;// true (readonly tuple)5051// 결론: `T extends readonly any[]`로는 tuple/array 구분 불가
타입 수준에서 배열과 튜플의 구분
그렇다면 타입 수준에서 배열과 튜플을 구분하는 방법은 무엇인가? 바로 length 프로퍼티의 타입을 보는 것이다.
튜플의 length는 숫자 리터럴로 1, 2, 3과 같은 정해진 숫자 값이 나오는 반면, 배열은 길이가 정해져있지 않으므로 number가 나온다.
1type IsTupleArray<T extends readonly any[]> =2number extends T['length'] ? "Array" : "Tuple"34const array = [1, 2, 3];5const tuple = [4, 5, 6] as const;67IsTupleArray<typeof tuple> // "Tuple"8IsTupleArray<typeof array> // "Array"
숫자 리터럴은 number에 포함되기 때문에 역으로 number extends T['length']를 해준 것이 보인다.