TypeScript, 클래스와 생성자 시그니쳐(Constructor Signature)

다음과 같은 클래스가 있습니다.

class Person {
    constructor(name: string) {}
}

위 클래스의 생성자를 눈 여겨 보기 바랍니다. 클래스의 생성자는 new 연산자와 함께 클래스를 함수처럼 호출하면 실행되는 함수입니다. 이렇게 생성되는 생성자의 일반적인 시그니쳐는 다음과 같습니다.

new (...args) => ReturnType

앞서 봤던 Person 클래스에 대한 생성자 시그니쳐는 다음과 같습니다.

new (name: string) => Person

사실 Person 클래스의 생성자 시그니처는 간단히 다음처럼 얻을 수 있습니다.

type T = typeof Person;

즉, T는 다음과 같게 됩니다.

type T = new(name: string) => Person;

JavaScript, ??= 와 ||= 그리고 &&= 연산자에 대하여

몰라도 내 코드 작성에 전혀 문제가 되지 않는 연산자이지만 다른 사람의 코드를 분석하다 보면 볼 수 있는 연산자이므로 알고는 있어야 하기에 정리해 봅니다.

먼저 ??= 연산자는 Nullish Coalescing Assignment(널 병합 대입 연산자) 입니다. 좌변이 null 이거나 undefined 일때만 우변을 대입합니다. 코드로 보면 …

x ??= y;

위의 코드는 다음 코드와 동일한 코드이며 아래 코드로 설명을 대신합니다.

if (x === null || x === undefined) {
  x = y;
}

위의 코드는 다음 코드와 같습니다.

x = x ?? y;

참고로 ?? 연산자는 Nullish Coalescing Operator(널 병합 연산자)자로 ??= 연산자의 형제이기도 합니다. 위의 코드는 다음과 같습니다.

const result = (a === null || a === undefined) ? b : a;

자바스크립트 특유의 falsy 값으로 판정되는 false, 빈문자열, 0, NaN, null, undefined 값 중 오직 null, undefined 만을 특별하게 취급해주는 것이 바로 ?? 연산자와 ??= 연산자입니다.

??= 연산자를 살펴보다 보면 ||= 연산자와 &&= 연산자도 함께 살펴봐야 혼란이 없습니다. 먼저 ||= 연산자는 좌변이 falsy 값이면 대입합니다. 예를 들어 다음 코드를 보면 …

let a = 0;
a ??= 100;

console.log(a); // 0

&&= 연산자는 ||= 연산자와는 반대로 좌변이 truthy(falsy 값이 아닌 값)이면 대입합니다. 예들 들어 다음 코드를 보면 …

let z = "abc";
z &&= "xyz";
console.log(z); // "xyz"

TypeScript, 제네릭에서 클래스 타입 사용 (Using Class Types in Generics)

제네릭의 타입 어노테이션에 클래스 타입을 받기 위해서는 클래스의 생성자 함수 타입을 타입 어노테이션으로 받으면 됩니다. 즉, 다음처럼요.

function create<Type>(c: { new (): Type }): Type {
  return new c();
}

위의 함수는 클래스 타입을 제네릭 타입으로 받아 해당 클래스의 객체를 생성해줍니다. 예제 코드는 다음과 같습니다.

class Animal {
  constructor(name: string) { 
    // ...
  }
}
 
function createInstance<A>(c: new (name: string) => A, name: string): A {
  return new c(name);
}
 
createInstance(Animal, "tiger");

Animal의 생성자가 string 타입의 인자를 받는다는 점에 고려하여 클래스의 생성자 함수 타입을 정의하고 있고 있습니다.

TypeScript, 제네릭 제약조건에서 타입 매개변수 사용 (Using Type Parameters in Generic Constraints)

function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key];
}

위의 함수는 Type에 대한 객체를 첫번째 인자로 받고 두번째 인자로 Type에 대한 프로퍼티의 키를 받습니다. 예들들면 다음과 같습니다.

let x = { a: 1, b: 2, c: 3, d: 4 };
 
getProperty(x, "a");
getProperty(x, "m");

위의 코드에서 4번째 코드는 “m” 형식의 인수는 “a” | “b” | “c” | “d” 형식의 매개 변수에 할당될 수 없습니다.”라는 에러가 발생합니다.

참고로 상기 코드에서 keyof가 사용되었는데, keyof 연산자는 객체 타입에서 객체의 키 값들을 숫자나 문자열 리터럴 유니언을 생성합니다. 아래 타입 P는 "x" | "y"와 동일한 타입입니다.

type Point = { x: number; y: number };
type P = keyof Point;

TypeScript, 제네릭(Generic) 함수에 대한 타입

다음과 같은 제니릭 함수가 있습니다.

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

상기 함수에 대한 타입은 어떻게 정의할 수 있을까.. 3가지 정도가 가능합니다. 먼저…

let myIdentity1: <T>(arg: T) => T = identity;

다음은 객체 리터럴 타입의 함수 호출 시그니처 방식입니다.

let myIdentity2: { <T>(arg: T): T } = identity;

다음은 인터페이스 방식입니다.

interface GenericIdentityFn1 {
  <T>(arg: T): T;
}
let myIdentity3: GenericIdentityFn1 = identity;

interface GenericIdentityFn2<T> {
  (arg: T): T;
}
let myIdentity4: GenericIdentityFn2<string> = identity;