TypeScript 메타 프로그래밍 기술 설명

메타프로그래밍은 프로그램이 스스로 또는 다른 프로그램을 조작할 수 있게 해주는 강력한 기술입니다. TypeScript에서 메타프로그래밍은 코드 유연성과 추상화를 향상시키기 위해 유형, 제네릭 및 데코레이터를 사용하는 기능을 말합니다. 이 문서에서는 TypeScript의 주요 메타프로그래밍 기술과 이를 효과적으로 구현하는 방법을 살펴봅니다.

1. 유연한 코드를 위한 제네릭 사용

제네릭은 함수와 클래스가 다양한 유형으로 작동할 수 있게 하여 유연성과 코드 재사용성을 높입니다. 유형 매개변수를 도입함으로써, 우리는 여전히 유형 안전성을 유지하면서 코드를 제네릭하게 만들 수 있습니다.

function identity<T>(arg: T): T {
  return arg;
}

const num = identity<number>(42);
const str = identity<string>("Hello");

이 예에서 <T>identity 함수가 모든 유형을 허용하고 동일한 유형을 반환하도록 하여 유연성과 유형 안전성을 보장합니다.

2. 유형 추론 및 조건 유형

TypeScript의 유형 추론 시스템은 표현식의 유형을 자동으로 추론합니다. 또한 조건부 유형은 조건에 따라 달라지는 유형을 생성하여 보다 진보된 메타프로그래밍 기술을 허용합니다.

type IsString<T> = T extends string ? true : false;

type Test1 = IsString<string>;  // true
type Test2 = IsString<number>;  // false

이 예에서 IsString은 주어진 유형 Tstring을 확장하는지 확인하는 조건 유형입니다. 문자열의 경우 true을 반환하고 다른 유형의 경우 false을 반환합니다.

3. 매핑된 유형

매핑된 유형은 유형의 속성을 반복하여 한 유형을 다른 유형으로 변환하는 방법입니다. 이는 기존 유형의 변형을 만드는 메타 프로그래밍에서 특히 유용합니다.

type ReadOnly<T> = {
  readonly [K in keyof T]: T[K];
};

interface User {
  name: string;
  age: number;
}

const user: ReadOnly<User> = {
  name: "John",
  age: 30,
};

// user.name = "Doe";  // Error: Cannot assign to 'name' because it is a read-only property.

여기서, ReadOnly는 주어진 유형의 모든 속성을 readonly로 만드는 매핑된 유형입니다. 이렇게 하면 이 유형의 객체는 속성을 수정할 수 없습니다.

4. 템플릿 리터럴 유형

TypeScript를 사용하면 템플릿 리터럴로 문자열 유형을 조작할 수 있습니다. 이 기능은 문자열 기반 작업에 대한 메타 프로그래밍을 가능하게 합니다.

type WelcomeMessage<T extends string> = `Welcome, ${T}!`;

type Message = WelcomeMessage<"Alice">;  // "Welcome, Alice!"

이 기술은 일관된 문자열 패턴에 의존하는 대규모 애플리케이션에서 일반적으로 사용되는, 문자열 유형을 동적으로 생성하는 데 유용할 수 있습니다.

5. 재귀적 유형 정의

TypeScript는 재귀적 유형을 허용하는데, 이는 자신을 참조하는 유형입니다. 이는 JSON 객체나 깊이 중첩된 데이터와 같은 복잡한 데이터 구조를 다루는 메타프로그래밍에 특히 유용합니다.

type Json = string | number | boolean | null | { [key: string]: Json } | Json[];

const data: Json = {
  name: "John",
  age: 30,
  friends: ["Alice", "Bob"],
};

이 예에서 Json은 유효한 JSON 데이터 구조를 나타낼 수 있는 재귀적 유형으로, 유연한 데이터 표현이 가능합니다.

6. 메타프로그래밍을 위한 데코레이터

TypeScript의 데코레이터는 클래스와 메서드를 수정하거나 주석을 달기 위해 사용되는 메타프로그래밍의 한 형태입니다. 이를 통해 동적으로 동작을 적용할 수 있으므로 로깅, 검증 또는 종속성 주입에 이상적입니다.

function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with`, args);
    return originalMethod.apply(this, args);
  };
}

class Calculator {
  @Log
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(2, 3);  // Logs: "Calling add with [2, 3]"

이 예에서 Log 데코레이터는 add 메서드가 호출될 때마다 메서드 이름과 인수를 기록합니다. 이는 메서드 코드를 직접 변경하지 않고도 동작을 확장하거나 수정하는 강력한 방법입니다.

결론

TypeScript의 메타프로그래밍 기능을 통해 개발자는 유연하고 재사용 가능하며 확장 가능한 코드를 작성할 수 있습니다. 제네릭, 조건부 유형, 데코레이터, 템플릿 리터럴 유형과 같은 기술은 견고하고 유지 관리 가능한 애플리케이션을 구축할 수 있는 새로운 가능성을 열어줍니다. 이러한 고급 기능을 마스터하면 프로젝트에서 TypeScript의 모든 잠재력을 활용할 수 있습니다.