Posts

[TS] 고급타입(Advanced Types) - 3

목차27

Recursive Types(재귀 타입)

Recursive Types(재귀 타입)는 자기 참조하는 타입을 정의할 수 있도록 해준다다. 이는 트리, 그래프 등과 같이 반복적인 형태의 데이터 구조를 다룰 때 유용하다.

예를 들어, 다음과 같은 트리 구조를 타입으로 표현할 수 있다.

코드
type TreeNode = {
  value: number;
  left?: TreeNode;
  right?: TreeNode;
}

위 타입 정의에서 TreeNode은 left와 right 프로퍼티가 자기 자신을 참조하는 타입이다. 이는 트리가 자식 노드를 가질 수 있기 때문에 가능하다.

Recursive Types은 무한히 중첩될 수 있기 때문에, 끝없이 반복되는 형태의 데이터 구조를 다루기에 적합하다.

하지만 이를 사용할 때에는 주의해야 할 점이 있다. 타입스크립트에서는 재귀적으로 정의된 타입이 무한히 반복되는 것을 막기 위해 타입 깊이(depth)를 제한하고 있다. 기본적으로는 50단계로 제한되어 있으며, 이를 초과할 경우 타입스크립트 컴파일러가 에러를 발생시킵니다.

Recursive Types은 인터페이스나 제네릭 타입에서도 사용할 수 있다. 아래는 인터페이스에서 Recursive Types을 사용한 예시이다.

코드
interface LinkedListNode<T> {
  value: T;
  next?: LinkedListNode<T>;
}

type IntLinkedListNode = LinkedListNode<number>;

위 예제에서는 LinkedListNode 인터페이스가 next 프로퍼티가 자기 자신을 참조하는 Recursive Types이다. 이를 기반으로 IntLinkedListNode 타입을 정의할 수 있다.

Recursive Types을 사용할 때는 재귀적으로 정의되는 타입이 올바르게 동작하도록 신중하게 설계해야 한다. 하지만 이를 사용함으로써 특정 형태의 데이터 구조를 타입으로 표현할 수 있어서 유용하다.


Tuple Types(튜플 타입)

튜플 타입(Tuple Types)은 고정된 개수의 요소(element)와 각 요소의 타입(type)이 미리 정의된 배열(array) 타입이다. 다른 배열 타입과는 달리, Tuple Types은 각 요소마다 다른 타입을 지정할 수 있다.

Tuple Types은 다음과 같이 정의할 수 있다.

코드
let myTuple: [string, number] = ["hello", 123];

위 예시에서, myTuple은 문자열(string)과 숫자(number) 두 요소로 이루어진 튜플 타입이다. 요소의 타입은 순서대로 배열 안에 지정된다. 이 경우, 첫 번째 요소는 문자열(string) 타입이며, 두 번째 요소는 숫자(number) 타입이다.

Tuple Types은 배열과 유사하게 인덱스(index)를 통해 각 요소에 접근할 수 있다. 이 때, 요소의 타입에 맞는 메소드나 속성을 사용할 수 있다.

코드
console.log(myTuple[0].toUpperCase()); // "HELLO"
console.log(myTuple[1].toFixed(2)); // "123.00"

Tuple Types은 고정된 개수의 요소와 각 요소의 타입이 미리 정의되어 있기 때문에, 배열 타입보다 더 구체적인 타입을 지정할 수 있다. 

Array과 다른 Tuple Types의 특징

  • 요소의 개수와 타입이 고정되어 있다.

Tuple Types은 요소의 개수와 타입이 미리 정해져 있기 때문에, 배열과는 다르게 요소의 개수나 타입을 동적으로 변경할 수 없다. 예를 들어, [string, number] 타입의 Tuple은 반드시 첫 번째 요소가 문자열이고 두 번째 요소가 숫자여야 한다.

  • 인덱스에 따라 요소의 타입이 결정된다.

Tuple Types에서는 배열과 마찬가지로 인덱스를 사용하여 각 요소에 접근할 수 있다. 이 때, 각 인덱스에 따라 요소의 타입이 결정된다. 예를 들어, [string, number] 타입의 Tuple에서 첫 번째 요소는 문자열이므로, myTuple[0]의 타입은 string이 된다.

  • 배열 타입보다 더 구체적인 타입을 지정할 수 있다.

Tuple Types은 요소의 개수와 타입이 미리 정해져 있기 때문에, 배열 타입보다 더 구체적인 타입을 지정할 수 있다. 예를 들어, string[] 타입은 문자열만 포함하는 배열을 의미하지만, [string, number] 타입의 Tuple은 문자열과 숫자를 각각 포함하는 배열을 의미한다.

  • 유틸리티 타입을 사용하여 Tuple Types을 조작할 수 있다.

타입스크립트는 유틸리티 타입(utility type)을 제공하여 Tuple Types을 조작할 수 있다. 예를 들어, readonly 유틸리티 타입을 사용하면 Tuple Types의 요소를 읽기 전용으로 만들 수 있다.

코드
let myTuple: readonly [string, number] = ["hello", 123];
myTuple[0] = "world"; // Error: Index signature in type 'readonly [string, number]' only permits reading.

위 예시에서, myTuple을 readonly [string, number] 타입으로 정의하면, 요소를 수정하는 것이 불가능해진다.

Tuple Types을 사용한 다양한 상황

  • 함수에서 여러 값을 반환할 때

일반적으로 함수에서는 하나의 값만 반환한다. 하지만, 때로는 함수에서 여러 값을 반환해야 하는 상황이 있을 수 있다.

이 때, Tuple Types을 사용하면 여러 값을 한 번에 반환할 수 있다.

코드
function getPerson(): [string, number] {
  return ["Alice", 30];
}

const [name, age] = getPerson();
console.log(name); // "Alice"
console.log(age); // 30

위 예시에서, getPerson 함수는 이름과 나이를 문자열과 숫자의 Tuple Types으로 반환한다. 반환된 Tuple Types을 구조분해할당을 사용하여 각 변수에 할당한다.

  • 고정된 개수의 요소를 포함하는 컬렉션

일반적으로 배열은 동적으로 크기가 조정될 수 있는 컬렉션이다. 하지만, 경우에 따라서는 고정된 개수의 요소를 포함하는 컬렉션이 필요할 수 있다. 이 때, Tuple Types을 사용하면 각 요소의 타입이 미리 정해져 있으므로 **타입 안정성(type safety)**이 높아진다.

코드
type Point = [number, number];

function getDistance(point1: Point, point2: Point): number {
  const dx = point2[0] - point1[0];
  const dy = point2[1] - point1[1];
  return Math.sqrt(dx ** 2 + dy ** 2);
}

const p1: Point = [0, 0];
const p2: Point = [3, 4];
console.log(getDistance(p1, p2)); // 5

위 예시에서, Point는 두 개의 숫자를 요소로 갖는 Tuple Types이다. getDistance 함수는 두 개의 Point 타입의 인수를 받아 두 점 사이의 거리를 계산한다.

Tuple Types은 고정된 개수의 요소와 각 요소의 타입이 미리 정의되어 있기 때문에, 코드의 가독성과 유지보수성을 높이는데 도움을 준다. 하지만, Tuple Types은 동적으로 요소를 추가하거나 제거하는 등의 작업이 어려운 점이 있으므로, 사용 시 주의해야 한다.


Utility Types(유틸리티 타입)

Utility Types(유틸리티 타입)은 일반적인 타입 선언을 보다 쉽고 간결하게 작성할 수 있도록 도와주는 타입이다. 유틸리티 타입은 기존 타입을 활용하여 새로운 타입을 만들어내는 방식으로 사용된다.

Partial<Type>

Type의 모든 속성을 optional로 만들어 새로운 타입을 생성한다.

코드
interface User {
  name: string;
  age: number;
}
type PartialUser = Partial<User>;
// PartialUser: { name?: string; age?: number; }

Required<Type>

Type의 모든 속성을 필수로 만들어 새로운 타입을 생성한다.

코드
interface User {
  name?: string;
  age?: number;
}
type RequiredUser = Required<User>; // RequiredUser: { name: string; age: number; }

Readonly<Type>

Type의 모든 속성을 읽기 전용으로 만들어 새로운 타입을 생성한다.

코드
interface User {
  name: string;
  age: number;
}
type ReadonlyUser = Readonly<User>; // ReadonlyUser: { readonly name: string; readonly age: number; }

Record<Keys, Type>

Type에 대한 Key-Value 쌍을 가진 객체를 생성한다.

코드
type User = { name: string; age: number };
type UserRecord = Record<string, User>; // UserRecord: { [key: string]: User; }

Exclude<Type, ExcludedUnion>

Type에서 ExcludedUnion에 속하는 타입을 제외한 새로운 타입을 생성한다.

코드
type Color = 'red' | 'blue' | 'green';
type ExcludedColor = Exclude<Color, 'green'>; // ExcludedColor: "red" | "blue"

Extract<Type, Union>

Union 타입(Type)에서 Type과 일치하는 타입을 추출하여 타입으로 사용한다.

코드
type MyUnion = 'a' | 'b' | 'c';
type MyExtract = Extract<'a' | 'c' | 'd', MyUnion>; // MyExtract: 'a' | 'c'

Pick<Type, Keys>

Type에서 지정한 속성만을 포함하는 새로운 타입을 생성한다.

코드
interface User {
  name: string;
  age: number;
  email: string;
}
type UserBasicInfo = Pick<User, 'name' | 'age'>; // UserBasicInfo: { name: string; age: number; }

Omit<Type, Keys>

Type에서 지정한 속성을 제외한 나머지 속성으로 새로운 타입을 생성한다.

코드
interface User {
  name: string;
  age: number;
  email: string;
}
type UserWithoutEmail = Omit<User, 'email'>; // UserWithoutEmail: { name: string; age: number; }

ReturnType<Type>

함수 타입(Type)의 반환 타입을 추출하여 타입으로 사용한다.

코드
function getUser() {
  return { name: 'John', age: 30 };
}
type User = ReturnType<typeof getUser>; // User: { name: string; age: number; }

Parameters<Type>

함수 타입(Type)의 매개변수 타입들을 튜플 형태로 추출하여 타입으로 사용한다.

코드
function getUser(name: string, age: number) {
  return { name, age };
}
type UserParams = Parameters<typeof getUser>; // UserParams: [string, number]

RequiredKeys<Type>

Type에서 속성값이 required인 키를 추출하여 타입으로 사용한다.

코드
interface User {
  name?: string;
  age: number;
  email: string;
}
type RequiredUserKeys = RequiredKeys<User>; // RequiredUserKeys: "age" | "email"

PartialKeys<Type>

Type에서 속성값이 optional인 키를 추출하여 타입으로 사용한다.

코드
interface User {
  name?: string;
  age: number;
  email: string;
}
type OptionalUserKeys = PartialKeys<User>; // OptionalUserKeys: "name"

NonNullable<Type>

Type에서 null, undefined가 아닌 타입을 추출하여 타입으로 사용한다.

코드
type User = { name: string | null | undefined; age: number | null | undefined };
type NonNullableUser = NonNullable<User['name'] | User['age']>; // NonNullableUser: string | number

InstanceType<Type>

클래스 타입(Type)에서 인스턴스 타입을 추출하여 타입으로 사용한다.

코드
class User {
  name: string;
  age: number;
}
type UserInstance = InstanceType<typeof User>; // UserInstance: User

ThisType<Type>

함수에서 this 타입(Type)을 명시적으로 지정하여 타입스크립트 컴파일러가 해당 함수 내에서 this로 사용하는 객체를 타입 체크할 수 있도록 한다.

코드
interface User {
  name: string;
  greet(this: User): void;
}
function greeting(this: User) {
  console.log('Hello, ' + this.name);
}
const user: User = { name: 'John', greet: greeting };
user.greet(); // Hello, John

ThisParameterType<Type>

함수 타입(Type)에서 this 매개변수 타입을 추출하여 타입으로 사용한다.

코드
function getUser(this: { name: string }) {
  return this.name;
}
type UserThisParam = ThisParameterType<typeof getUser>; // UserThisParam: { name: string }

OmitThisParameter<Type>

함수 타입(Type)에서 this 매개변수를 제거하여 새로운 함수 타입(Type)을 생성한다.

코드
function getUser(this: { name: string }) {
  return this.name;
}
type UserWithoutThis = OmitThisParameter<typeof getUser>; // UserWithoutThis: () => string

UnionToIntersection<Union>

Union 타입(Union)에서 인터섹션 타입(Intersection)을 추출하여 타입으로 사용한다.

코드
type MyUnion = { name: string } | { age: number };
type MyIntersection = UnionToIntersection<MyUnion>; // MyIntersection: { name: string } & { age: number }

First<Type>

Tuple 타입(Type)에서 첫 번째 요소의 타입을 추출하여 타입으로 사용한다.

코드
type MyTuple = [string, number, boolean];
type MyFirst = First<MyTuple>; // MyFirst: string

Last<Type>

Tuple 타입(Type)에서 마지막 요소의 타입을 추출하여 타입으로 사용한다.

코드
type MyTuple = [string, number, boolean];
type MyLast = Last<MyTuple>; // MyLast: boolean

Append<Tuple, Addendum>

Tuple 타입(Tuple)에 다른 타입(Addendum)을 추가하여 새로운 Tuple 타입(Tuple)을 생성한다.

코드
type MyTuple = [string, number];
type MyTupleWithBoolean = Append<MyTuple, boolean>; // MyTupleWithBoolean: [string, number, boolean]

Prepend<Tuple, Addendum>

Tuple 타입(Tuple)의 첫 번째에 다른 타입(Addendum)을 추가하여 새로운 Tuple 타입(Tuple)을 생성한다.

코드
type MyTuple = [number, boolean];
type MyTupleWithBoolean = Prepend<MyTuple, string>; // MyTupleWithBoolean: [string, number, boolean]

OptionalKeys<Type>

객체 타입(Type)에서 속성 이름이 Optional(?)인 속성들의 이름을 문자열 리터럴의 유니온 타입으로 추출한다.

코드
interface MyInterface {
  name: string;
  age?: number;
  gender?: string;
}
type MyOptionalKeys = OptionalKeys<MyInterface>; // MyOptionalKeys: 'age' | 'gender'

ConstructorParameters<Type>

생성자 함수(Type)의 매개변수 타입들을 Tuple 타입으로 추출한다.

코드
class MyClass {
  constructor(name: string, age: number) {}
}
type MyConstructorParams = ConstructorParameters<typeof MyClass>; // MyConstructorParams: [string, number]

반응형