TypeScript의 타입 좁히기(Narrowing)

원도훈·2024년 12월 10일
1

안녕하세요, TypeScript의 중요한 개념 중 하나인 타입 좁히기(Narrowing)에 대해 깊이 알아보겠습니다. TypeScript의 타입 좁히기는 조건문 등을 통해 변수의 타입을 더 구체적으로 좁혀가는 과정으로, 코드의 안전성과 가독성을 높이는 데 큰 도움을 줍니다. 이번 글에서는 타입 좁히기의 정의와 다양한 사용 사례, 실무 팁까지 차근차근 살펴보겠습니다.


타입 좁히기란 무엇인가?

TypeScript에서 타입 좁히기(Narrowing)란 코드의 실행 흐름을 따라 변수의 타입을 더 구체적으로 추론하거나 명시하는 과정을 의미합니다. TypeScript는 변수의 초기 타입이 string | number와 같은 유니언 타입일 때, 조건문이나 타입 검사 등을 통해 해당 타입을 점점 구체화할 수 있습니다.

예제

function printLength(value: string | number): void {
  if (typeof value === 'string') {
    // value는 string 타입으로 좁혀짐
    console.log(`문자열의 길이: ${value.length}`);
  } else {
    // value는 number 타입으로 좁혀짐
    console.log(`숫자의 자릿수: ${value.toString().length}`);
  }
}

printLength('Hello'); // 문자열의 길이: 5
printLength(12345); // 숫자의 자릿수: 5

위 예제에서 typeof를 사용하여 value의 타입을 검사한 후, 각 조건문 내부에서 value는 해당 타입으로 좁혀집니다. 이를 통해 안전하게 타입에 맞는 작업을 수행할 수 있습니다.


타입 좁히기의 주요 방법

1. typeof 검사

typeof 연산자를 사용하면 변수의 기본 타입을 확인할 수 있습니다. 이는 string, number, boolean, object, undefined, function 등을 검사하는 데 사용됩니다.

function processValue(value: string | number | boolean): void {
  if (typeof value === 'string') {
    console.log(`문자열 처리: ${value.toUpperCase()}`);
  } else if (typeof value === 'number') {
    console.log(`숫자 처리: ${value * 2}`);
  } else {
    console.log(`불리언 처리: ${value ? '참' : '거짓'}`);
  }
}

2. instanceof 검사

instanceof 연산자를 사용하면 객체가 특정 클래스의 인스턴스인지 확인할 수 있습니다. 이를 통해 객체의 타입을 좁힐 수 있습니다.

class Dog {
  bark() {
    console.log('멍멍!');
  }
}

class Cat {
  meow() {
    console.log('야옹!');
  }
}

function makeSound(animal: Dog | Cat): void {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.meow();
  }
}

3. 사용자 정의 타입 가드

사용자 정의 타입 가드는 함수 내부에서 특정 조건에 따라 타입을 좁히는 데 사용됩니다. 반환 타입을 param is Type 형식으로 지정하여 타입스크립트에 타입 좁히기를 명시합니다.

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function logValue(value: string | number): void {
  if (isString(value)) {
    console.log(`문자열: ${value.toUpperCase()}`);
  } else {
    console.log(`숫자: ${value * 2}`);
  }
}

4. in 연산자

in 연산자를 사용하면 객체에 특정 속성이 존재하는지 확인하여 타입을 좁힐 수 있습니다.

interface Car {
  drive(): void;
}

interface Boat {
  sail(): void;
}

function operate(vehicle: Car | Boat): void {
  if ('drive' in vehicle) {
    vehicle.drive();
  } else {
    vehicle.sail();
  }
}

5. 엄격한 null 검사

strictNullChecks 옵션이 활성화된 경우, TypeScript는 nullundefined를 명시적으로 처리해야 합니다.

function greet(name: string | null): void {
  if (name !== null) {
    console.log(`안녕하세요, ${name}!`);
  } else {
    console.log('안녕하세요!');
  }
}

타입 좁히기와 타입 단언의 차이점

타입 좁히기와 타입 단언(Type Assertion)은 유사한 목적을 가질 수 있지만, 동작 방식과 의도가 다릅니다.

  • 타입 좁히기: 조건문이나 타입 가드 등을 통해 코드의 흐름에 따라 타입을 구체화합니다.
  • 타입 단언: 개발자가 특정 타입임을 "확신"하여 컴파일러에게 명시적으로 타입을 지정합니다. 런타임 검사는 수행되지 않습니다.
const input = document.getElementById('user-input') as HTMLInputElement;
input.value = 'Hello'; // 타입 단언

타입 단언은 잘못된 사용 시 런타임 오류를 초래할 수 있으므로, 가능한 타입 좁히기를 우선적으로 사용하는 것이 좋습니다.


실무에서 타입 좁히기 활용하기

  1. 유니언 타입 처리: string | number와 같은 유니언 타입을 다룰 때 타입 좁히기를 사용하면 코드가 더 안전하고 명확해집니다.
  2. 객체 속성 검사: 다양한 인터페이스를 처리할 때, in 연산자와 사용자 정의 타입 가드를 활용하면 오류를 줄일 수 있습니다.
  3. 복잡한 조건 처리: 다중 조건문에서 타입 좁히기를 활용하면 예상치 못한 타입 오류를 방지할 수 있습니다.

결론

TypeScript의 타입 좁히기는 타입 안전성과 코드 가독성을 높이는 데 매우 유용한 도구입니다.

  • typeof, instanceof, in 연산자와 사용자 정의 타입 가드를 활용해 유연하고 안전한 코드를 작성할 수 있습니다.
  • 타입 좁히기를 통해 조건에 따라 타입을 구체화하면 런타임 오류를 효과적으로 방지할 수 있습니다.

타입 좁히기를 적극적으로 활용하여 더욱 안정적이고 유지보수하기 쉬운 TypeScript 코드를 작성해 보세요! 🙌


참고 자료

profile
개발

0개의 댓글