[TypeScript] 고급 타입

LMH·2023년 4월 5일
0
post-thumbnail

Typescript는 강력한 타입 시스템을 가지고 있어서, 고급 타입 기능을 제공합니다. 고급타입에는 맣은 종류가 있지만 이번에는 Typescript에서 제공하는 많이 사용되는 고급타입 몇 가지에 대해서 알아 보겠습니다.

제네릭 (Generics)

제네릭은 함수나 클래스에서 사용할 타입을 외부에서 지정하는 기능입니다. 이를 통해 재사용성이 높은 함수나 클래스를 작성할 수 있습니다. 예를 들어, 아래와 같은 함수를 작성할 수 있습니다.

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

let output = identity<string>("hello world");

제네릭은 와 같이 꺾쇠 괄호 안에 타입 변수를 지정하여 사용합니다. 이 때 타입 변수는 함수 호출 시에 실제 타입으로 대체됩니다.

유니온 타입 (Union Types)

유니온 타입은 여러 타입 중 하나일 수 있는 값을 나타내는 타입입니다. 이를 통해 다양한 타입의 값을 처리할 수 있습니다. 예를 들어, 아래와 같이 두 개의 타입을 유니온 타입으로 지정할 수 있습니다.

function printID(id: string | number) {
  console.log(id);
}

printID("123"); // 출력: 123
printID(456);   // 출력: 456

교차 타입 (Intersection Types)

교차 타입은 여러 타입의 속성을 모두 갖는 타입입니다. 이를 통해 두 객체를 합치는 등의 연산이 가능합니다. 예를 들어, 아래와 같이 두 개의 인터페이스를 교차 타입으로 지정할 수 있습니

interface A {
a: number;
}

interface B {
b: string;
}

type C = A & B;

let c: C = { a: 123, b: "hello" };
console.log(c); // 출력: { a: 123, b: 'hello' }

타입 가드 (Type Guards)

타입 가드는 타입의 범위를 좁혀주는 기능입니다. 이를 통해 유니온 타입 등에서 해당 타입으로 좁힐 수 있습니다. 예를 들어, 아래와 같이 typeof 연산자를 이용하여 타입 가드를 작성할 수 있습니다.

typeof 타입 가드

number, string, boolean, symbol 타입의 경우 if문과 tpyeof를 이용하여 특정 타입을 지정해 줄 수 있습니다.

function printID(id: string | number) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id.toFixed(2));
  }
}

printID("123"); // 출력: "123"
printID(456);   // 출력: "456.00"

instanceof 타입 가드

instanceof를 사용하면 어떤 객체가 특정 클래스의 인스턴스인지 여부를 확인할 수 있습니다. 아래 예제를 통해 instanceof를 사용한 타입 가드를 살펴보겠습니다.

class Animal {
constructor(public name: string) {}
}

class Dog extends Animal {
bark() {
  console.log('Woof!');
}
}

class Cat extends Animal {
meow() {
  console.log('Meow!');
}
}

function makeSound(animal: Animal) {
if (animal instanceof Dog) {
  animal.bark(); // animal이 Dog 타입이면 bark() 메소드를 호출합니다.
} else if (animal instanceof Cat) {
  animal.meow(); // animal이 Cat 타입이면 meow() 메소드를 호출합니다.
}
}

const dog = new Dog('Buddy');
const cat = new Cat('Lucy');

makeSound(dog); // "Woof!"
makeSound(cat); // "Meow!"

위 예제에서 makeSound 함수는 Animal 타입을 인자로 받습니다. 함수 내부에서 animal 객체가 Dog 타입이면 bark() 메소드를 호출하고, Cat 타입이면 meow() 메소드를 호출합니다. 이를 위해 if (animal instanceof Dog)와 else if (animal instanceof Cat)를 사용하여 animal 객체가 어떤 클래스의 인스턴스인지 여부를 확인하고, 이를 통해 타입을 추론합니다. 이는 instanceof를 사용하여 타입 가드를 구현한 것입니다.

조건부 타입(Conditional Types)

조건부 타입은 타입을 조건에 따라 결정하는 방법입니다. 이를 통해 타입을 동적으로 결정하거나, 더 복잡한 타입 변환을 수행할 수 있습니다.
조건부 타입은 보통 다음과 같은 경우에 사용됩니다. 주로 제네릭 타입과 함께 많이 사용 됩니다.

type NonNullable<T> = T extends null | undefined ? never : T;

let str: string | null = "hello";
let num: number | undefined = undefined;

let nonNullableStr: NonNullable<typeof str>; // nonNullableStr의 타입은 string
let nonNullableNum: NonNullable<typeof num>; // nonNullableNum의 타입은 number

위의 코드에서 NonNullable 타입은 T 타입이 null 또는 undefined일 경우 never 타입으로 변환하고, 그 외에는 T 타입 그대로를 반환하는 타입입니다. NonNullable과 NonNullable은 각각 string과 number 타입으로 추론됩니다.

Nullable tyep

Nullable tyep은 변수 또는 속성이 null 값을 가질 수 있는지 여부를 지정하는 방법입니다.

nullable type은 | null 형태로 선언할 수 있습니다. 예를 들어, 문자열 변수가 null 값을 가질 수 있다면 다음과 같이 nullable type으로 선언할 수 있습니다.

let myString: string | null = "hello";
myString = null; // 가능

타입스크립트 2.0 이상부터는 --strictNullChecks 플래그가 기본적으로 설정되어 있어서 nullable type을 명시하지 않으면 null이 할당될 수 없는 타입으로 간주됩니다. 이 플래그를 해제하면 nullable type을 명시하지 않아도 null 값을 할당할 수 있습니다.

let myString = "hello";
myString = null; // 오류 발생

nullable type은 코드를 더욱 안정적으로 만들어줍니다. null 값의 처리를 더 명확하게 지정할 수 있으므로 코드 오류를 줄일 수 있습니다.

인덱스 타입(Index types)

인덱스 타입(Index Type)은 인덱스로 객체의 속성에 접근할 수 있는 타입입니다. 객체의 속성에 접근할 때, 해당 속성의 이름이 변수에 저장되어 있을 때 사용할 수 있습니다.

인덱스 타입은 keyof 키워드와 함께 사용됩니다. 예를 들어, 다음과 같이 Person 객체의 속성 중 하나를 문자열 인덱스로 사용하여 값을 가져오는 함수가 있다고 가정해보겠습니다.

interface Person {
  name: string;
  age: number;
  gender: 'male' | 'female';
}

function getValue(obj: Person, key: string) {
  return obj[key];
}

위 코드는 key 매개변수에 문자열을 전달하므로, TypeScript는 obj 객체의 속성 중 key에 해당하는 속성이 존재하는지 알 수 없습니다. 이때, keyof 키워드를 사용하여 객체의 모든 속성 이름을 타입으로 가져와서 인덱스 타입으로 사용할 수 있습니다.

interface Person {
  name: string;
  age: number;
  gender: 'male' | 'female';
}

function getValue(obj: Person, key: keyof Person) {
  return obj[key];
}
  

이제 key 매개변수는 Person 인터페이스의 속성 중 하나인 name, age, gender 중 하나를 전달해야 합니다. 이를 통해 TypeScript는 obj 객체의 속성 중 key에 해당하는 속성이 존재하는지 알 수 있게 됩니다.

Mapped types

Mapped Type은 TypeScript에서 기존 타입을 변환하여 새로운 타입을 만들 수 있는 기능입니다. 객체의 속성을 쉽게 변경하거나 추가하는 데 사용됩니다.

Mapped Type은 { [Key in Type]: NewType }의 형태로 사용됩니다. 이 때 Key는 새롭게 생성될 객체의 속성 이름, Type은 기존 객체의 속성 이름을 가지고 있는 타입, NewType은 Type을 변환하여 생성된 새로운 객체의 속성 타입입니다.

예를 들어, 다음과 같은 Person 인터페이스가 있다고 가정해보겠습니다.

interface Person {
  name: string;
  age: number;
  gender: 'male' | 'female';
}

이제 Person 인터페이스의 모든 속성을 string 타입으로 바꾸기 위해서는 다음과 같이 Mapped Type을 사용할 수 있습니다.


type PersonToString = { [Key in keyof Person]: string };
이제 PersonToString 타입은 다음과 같이 정의됩니다.

type PersonToString = {
  name: string;
  age: string;
  gender: string;
};

Mapped Type을 사용하면 기존 객체의 속성을 쉽게 변환하거나 추가할 수 있습니다. 다양한 상황에서 유용하게 사용할 수 있습니다.

Reference

https://velog.io/@zeros0623/TypeScript-%EA%B3%A0%EA%B8%89-%ED%83%80%EC%9E%85

profile
새로운 것을 기록하고 복습하는 공간입니다.

0개의 댓글