티스토리 뷰

🐶 조건부 타입

extends와 삼항 연산자를 이용해 조건에 따라 각각 다른 타입을 결정하는 문법

 

type A = number extends string? string : number;
// numebr는 string를 확장하는 타입(서브타입)이 아님 -> number 타입으로 정의
  • number extends string? 
    • 조건이 참이라면 string 타입으로 설정
    • 조건이 거짓이라면 number 타입으로 설정

number 타입은 string 타입의 서브 타입이 아니므로 타입 A는 number타입이 된다.

 

 

type ObjA = {
    a: number;
}

type ObjB = {
    a: number;
    b: number;
}

type B = ObjB extends ObjA? number : string;
// objB는 objA를 확장한 타입 -> number 타입으로 정의

객체 타입도 마찬가지로 ObjB는 ObjA 타입의 서브타입이므로 조건식에 따라 타입 B가 결정된다.

  • ObjB extends ObjA?
    • 조건이 참이라면 number 타입으로 결정
    • 조건이 거짓이라면 string 타입으로 결정

 

 

 

 

 

 

🐶 제네릭 조건부 타입

조건부 타입은 기본 타입과 사용할 때보다 제네릭과 함께 쓸 때 더 활용도가 높다.

 

type StringNumberSwitch<T> = T extends number? string : number;

let varA: StringNumberSwitch<number>; // number 타입으로 정의
let varB: StringNumberSwitch<string>; // string 타입으로 정의

타입 변수에 Number 타입이 할당되면 String 타입을 반환하고 그렇지 않다면 Number 타입을 반환하는 조건부 타입이다.

  • varA는 T에 number 타입을 할당 -> 조건식이 참이 되므로 string 타입
  • varB는 T에 string 타입을 할당 -> 조건식이 거짓이 되므로 number 타입

 

 

function removeSpaces(text: string) {
  return text.replaceAll(' ', '');
}

let result = removeSpaces('hi im kiji');

매개변수로 String 타입의 값을 받아 공백을 제거해 반환하는 함수 removeSpaces를 구현했다.

 

이때 이 함수의 매개변수에 undefined나 null 타입의 값들도 제공될 수 있다고 가정하고 코드를 수정한다.

function removeSpaces(text: string | undefined | null) {
  return text.replaceAll(' ', ''); // ❌ test가 string이 아닐 수 있음
}

let result = removeSpaces('hi im kiji');

함수 내부에서 text 타입은 String이 아니라 undefined나 null이 될 수도 있기 때문에 오류가 발생한다.

 

이러한 경우 타입을 좁혀 사용한다.

function removeSpaces(text: string | undefined | null) {
  if (typeof text === 'string') {
    return text.replaceAll(' ', '');
  } else {
    return undefined;
  }
} 

let result = removeSpaces('hi im kiji');
// string | undefined

하지만 이 경우 변수 result의 타입이 string | undefiend 타입으로 추론된다.

 

이를 해결하기 위해 타입 변수를 추가한 후 조건부 타입을 이용해 인수로 전달된 값의 타입이 String이면 반환값 타입도 String이고 아닌 경우 반환값 타입을 undefined로 만든다.

 

function removeSpaces<T>(text: T): T extends string? string : undefined{
    if(typeof text === 'string'){
        return text.replaceAll(' ','') as any; 
        // 조건부 타입의 결과를 내부에서 알 수 없으므로 오류 발생
        // -> 타입 단언을 이용
    } else{
        return undefined as any;
        // 조건부 타입의 결과를 내부에서 알 수 없으므로 오류 발생
        // -> 타입 단언을 이용
    }
}

let result = removeSpaces('hi im kiji');
result.toUpperCase();

let result2 = removeSpaces(undefined);

함수 내부에서는 조건부 타입의 결과를 알 수 없어 return문에서 오류가 발생하기 때문에 타입 단언을 이용해 반환값 타입을 any 타입으로 단언한다.

  • result는 인수로 String 타입을 전달 -> 반환값의 타입은 String
  • result2는 인수로 undefined를 전달 -> 반환값의 타입은 undefiend

하지만 any로 단언하는 것은 좋지 못한다. 첫 번째 return문에서 string이 아닌 타입의 값을 반환해도 오류를 감지하지 못하기 때문이다.

따라서 타입 단언보다는 함수 오버로딩을 이용한다.

// 오버로드 시그니쳐 사용하기
// 구현 시그니쳐 내부에서 추론이 가능함
function removeSpaces<T>(text: T): T extends string? string : undefined;
function removeSpaces(text: any) {
    if(typeof text === 'string'){
        return text.replaceAll(' ','');
        // return text.replaceAll(' ','')as any;
        // 조건부 타입의 결과를 함수 내부에서 알 수 없음 -> 타입 단언 이용하기 
        // 하지만 as any의 사용은 좋지 않음 -> 함수 오버로딩 구현하기
    } else{
        return undefined;
    }
}

let result = removeSpaces('hi im kiji');
result.toUpperCase();

let result2 = removeSpaces(undefined);

오버로드 시그니쳐의 조건부 타입은 구현 시그니쳐 내부에서 추론이 가능하기 때문에 오버로드 시그니쳐를 추가해 함수 오버로딩을 구현한다.

 

 

 

 

 

 

 

 


🔗 타입스크립트 강의

 

한 입 크기로 잘라먹는 타입스크립트(TypeScript) - 인프런 | 강의

문법을 넘어 동작 원리와 개념 이해까지 배워도 배워도 헷갈리는 타입스크립트 이제 제대로 배워보세요! 여러분을 타입스크립트 마법사🧙🏻‍♀️로 만들어드립니다., 프론트엔드의 피할 수

www.inflearn.com

 

728x90