TypeScript Tips

김병엽·2024년 9월 24일

배워두면 유용한 타입스크립트의 여러 가지 기능을 알아보자.


Advanced Types

TypeScript의 고급 타입을 사용하면 기존 타입을 기반으로 새로운 타입을 만들 수 있다.

Mapped Types과 Conditional Types을 활용해 타입을 변경하고 조작할 수 있으며, 이를 통해 코드의 유연성과 유지보수성을 높일 수 있다.


Mapped Types

  • 매핑된 타입은 기존 타입의 속성을 순회하면서 변환을 적용해 새로운 타입을 만드는 방법.
  • 주로 읽기 전용 버전의 타입을 생성할 때 자주 사용.

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};
interface Point {
  x: number;
  y: number;
}

type ReadonlyPoint = Readonly<Point>;

Readonly는 제네릭 타입 T를 받아, 모든 속성을 읽기 전용으로 만드는 매핑된 타입이다.

ReadonlyPoint는 Point 인터페이스의 속성이 읽기 전용인 타입이다.


Conditional Types

  • 조건부 타입은 조건을 기반으로 새로운 타입을 생성.
  • 문법은 삼항 연산자와 비슷하며, extends 키워드를 사용해 타입 제약을 추가.
type NonNullable<T> = T extends null | undefined ? never : T;

NonNullable는 타입 Tnull 또는 undefined를 상속하면 never 타입을, 그렇지 않으면 T 타입을 반환한다.

interface Point {
  x: number;
  y: number;
}

type ReadonlyPoint = Readonly<Point>;

const regularPoint: Point = {
  x: 5,
  y: 10
};

const readonlyPoint: ReadonlyPoint = {
  x: 20,
  y: 30
};

regularPoint.x = 15; // 가능: 'Point'의 속성은 수정 가능
console.log(regularPoint); // 출력: { x: 15, y: 10 }

// readonlyPoint.x = 25; // 오류: 읽기 전용 속성은 수정 불가
console.log(readonlyPoint); // 출력: { x: 20, y: 30 }

function movePoint(p: Point, dx: number, dy: number): Point {
  return { x: p.x + dx, y: p.y + dy };
}

const movedRegularPoint = movePoint(regularPoint, 3, 4);
console.log(movedRegularPoint); // 출력: { x: 18, y: 14 }

// const movedReadonlyPoint = movePoint(readonlyPoint, 3, 4); // 오류: 'ReadonlyPoint'는 'Point'로 변환 불가

Readonly 타입을 사용하면 객체의 모든 속성이 수정 불가능해져 immutability(불변성)를 강제합니다.

ReadonlyPoint는 읽기 전용 속성이기 때문에 수정할 수 없으며, movePoint 함수와 같이 매개변수를 필요로 하는 함수에는 사용할 수 없다.


Decorators

TypeScript의 데코레이터는 클래스, 메서드, 속성, 매개변수에 메타데이터를 추가하거나 동작을 수정 및 확장할 수 있는 강력한 기능이다.

데코레이터는 고차 함수로, 클래스 정의, 메서드 정의, 접근자 정의, 속성 정의, 매개변수 정의를 관찰, 수정 또는 교체하는 데 사용할 수 있다.


Class Decorators

  • 클래스 데코레이터는 클래스의 생성자에 적용되어 클래스 정의를 수정하거나 확장 가능.
function LogClass(target: Function) {
  console.log(`Class ${target.name} was defined.`);
}

@LogClass
class MyClass {
  constructor() {}
}

// 출력: Class MyClass was defined.

이 데코레이터는 데코레이터가 적용된 클래스가 정의될 때 클래스 이름을 로그로 출력한다.


Method Decorators

  • 메서드 데코레이터는 클래스의 메서드에 적용되어 메서드 정의를 수정하거나 확장 가능.
function LogMethod(target: any, key: string, descriptor: PropertyDescriptor) {
  console.log(`Method ${key} was called.`);
}

class MyClass {
  @LogMethod
  myMethod() {
    console.log("Inside myMethod.");
  }
}

const instance = new MyClass();
instance.myMethod();

// 출력: 
// Method myMethod was called.
// Inside myMethod.

LogMethod는 메서드 데코레이터로, 메서드가 호출될 때 메서드 이름을 로그로 출력한다.


Property Decorators

  • 속성 데코레이터는 클래스의 속성에 적용되어 속성 정의를 수정하거나 확장 가능.
function DefaultValue(value: any) {
  return (target: any, key: string) => {
    target[key] = value;
  };
}

class MyClass {
  @DefaultValue(42)
  myProperty: number;
}

const instance = new MyClass();
console.log(instance.myProperty);

// 출력: 42

DefaultValue 속성 데코레이터는 속성에 기본값을 설정한다.


Parameter Decorators

  • 매개변수 데코레이터는 메서드 또는 생성자의 매개변수에 적용되어 매개변수 정의를 수정하거나 확장 가능.
function LogParameter(target: any, key: string, parameterIndex: number) {
  console.log(`Parameter at index ${parameterIndex} of method ${key} was called.`);
}

class MyClass {
  myMethod(@LogParameter value: number) {
    console.log(`Inside myMethod with value ${value}.`);
  }
}

const instance = new MyClass();
instance.myMethod(5);

// 출력:
// Parameter at index 0 of method myMethod was called.
// Inside myMethod with value 5.

LogParameter는 매개변수 데코레이터로, 메서드가 호출될 때 해당 매개변수의 인덱스와 메서드 이름을 로그로 출력합니다.


데코레이터는 클래스, 메서드, 속성, 매개변수의 동작을 수정하거나 확장할 수 있는 TypeScript의 고급 기능이다.

@ 문법을 통해 적용되며, 코드의 동작을 유연하게 제어하는 데 유용하다.


Namespace

TypeScript에서 네임스페이스는 관련된 코드를 조직화하고 그룹화하는 방법이다.

이를 통해 이름 충돌을 방지하고 모듈화를 촉진할 수 있다. 네임스페이스는 클래스, 인터페이스, 함수, 변수 및 다른 네임스페이스를 포함할 수 있다.


Defining Namespaces

  • namespace 키워드를 사용하여 네임스페이스를 정의.
  • 네임스페이스 내에 관련 코드를 추가할 수 있으며, 외부에서 사용할 수 있도록 export 키워드를 사용.
namespace MyNamespace {
  export class MyClass {
    constructor(public value: number) {}

    displayValue() {
      console.log(`The value is: ${this.value}`);
    }
  }
}

Using Namespaces

네임스페이스 내부 코드를 사용할 때, 두 가지 방법이 있다.

  • 첫 번째: 전체 경로(fully-qualified name)를 사용하는 것.
  • 두 번째: 네임스페이스 가져오기를 사용하는 것.
// 전체 경로 사용
const instance1 = new MyNamespace.MyClass(5);
instance1.displayValue(); // 출력: The value is: 5

// 네임스페이스 import 사용
import MyClass = MyNamespace.MyClass;

const instance2 = new MyClass(10);
instance2.displayValue(); // 출력: The value is: 10

Nested Namespaces

  • 네임스페이스는 중첩이 가능하여 코드 조직을 더욱 세분화할 수 있다.
namespace OuterNamespace {
  export namespace InnerNamespace {
    export class MyClass {
      constructor(public value: number) {}

      displayValue() {
        console.log(`The value is: ${this.value}`);
      }
    }
  }
}

// 전체 경로 사용
const instance = new OuterNamespace.InnerNamespace.MyClass(15);
instance.displayValue(); // 출력: The value is: 15

네임스페이스는 관련된 코드를 그룹화하고 이름 충돌을 방지하는 데 사용된다.

export 키워드를 사용하면 네임스페이스 밖에서 코드를 사용할 수 있다.

네임스페이스는 전체 경로를 사용하거나 import를 통해 사용이 가능하다.

중첩 네임스페이스는 코드 구조를 계층적으로 만들어 더 체계적인 코드를 작성할 수 있게 해준다.


Mixins

믹스인은 TypeScript에서 여러 작은 부분으로부터 클래스를 구성하는 방법다.

믹스인 클래스를 사용하면 클래스 간에 동작을 재사용하고 공유할 수 있으며, 이를 통해 모듈성 및 코드 재사용성을 높일 수 있다.


Defining Mixins

  • 믹스인 클래스를 정의하려면, 생성자 서명을 가진 제네릭 타입 매개변수를 확장하는 클래스를 생성.
  • 이를 통해 믹스인 클래스를 다른 클래스와 결합.
class TimestampMixin<TBase extends new (...args: any[]) => any>(Base: TBase) {
  constructor(...args: any[]) {
    super(...args);
  }

  getTimestamp() {
    return new Date();
  }
}

Using Mixins

  • 믹스인 클래스를 사용하려면, 기본 클래스를 정의하고 믹스인 클래스를 extends 키워드를 사용해 적용.
class MyBaseClass {
  constructor(public value: number) {}

  displayValue() {
    console.log(`The value is: ${this.value}`);
  }
}

class MyMixedClass extends TimestampMixin(MyBaseClass) {
  constructor(value: number) {
    super(value);
  }
}

const instance = new MyMixedClass(42);
instance.displayValue(); // Output: The value is: 42
const timestamp = instance.getTimestamp();
console.log(`The timestamp is: ${timestamp}`); // Output: The timestamp is: [현재 날짜 및 시간]

이 클래스는 MyBaseClassdisplayValue 메서드와 TimestampMixingetTimestamp 메서드를 모두 포함한다.


믹스인은 여러 클래스를 조합하여 하나의 클래스로 만들 수 있는 기능으로, 코드 재사용성과 모듈성을 높인다.

믹스인 클래스는 제네릭을 사용해 다양한 클래스와 결합할 수 있으며, 결합된 클래스는 믹스인의 기능을 상속받는다.


Type Guards

TypeScript에서 변수 또는 매개변수의 타입을 좁히는 방법이다.

이를 통해 서로 다른 타입을 구별하고, 특정 타입에 맞는 속성이나 메서드를 사용할 수 있어 타입 안정성을 높이고 런타임 오류를 줄일 수 있다.


Defining Type Guards

  • 타입 가드를 정의하려면, 매개변수를 받아 타입 서술자(type predicate)를 반환하는 함수를 생성.
function isString(value: any): value is string {
  return typeof value === "string";
}

함수는 value is string이라는 타입 서술자를 반환하여 value 변수가 문자열 타입으로 좁혀지게 한다.


Using Type Guards

  • 타입 가드를 사용하려면 if문이나 switch문 같은 조건문에서 타입 가드 함수를 호출.
function processValue(value: string | number) {
  if (isString(value)) {
    console.log(`The length of the string is: ${value.length}`);
  } else {
    console.log(`The square of the number is: ${value * value}`);
  }
}

processValue("hello"); // Output: The length of the string is: 5
processValue(42); // Output: The square of the number is: 1764

isString 타입 가드가 값을 확인하여, 타입에 맞는 코드 블록이 실행되고, 각 타입에 맞는 속성이나 메서드를 사용할 수 있게 한다.


Type Guards는 코드 내에서 변수의 타입을 안전하게 좁혀주는 기능이다.

타입 가드 함수는 매개변수가 특정 타입임을 확인하고, 타입에 맞는 속성이나 메서드를 사용할 수 있도록 해준다.

조건문에서 타입 가드를 사용하여 런타임 오류 없이 타입에 맞는 코드를 실행할 수 있다.


Reference

Medium/Marcos Vinicius Gouvea

profile
선한 영향력을 줄 수 있는 개발자가 되자, 되고싶다.

0개의 댓글