본문 바로가기

4주차

타입 좁히기 -> 타입 가드에 대해 알아보자!

안녕하세요 웹 YB 박희선입니다 😃

 

저는 현재 웹부스 스터디에 참여 중인데요, 바로 지난주 공부 범위에 타입 가드가 포함되어 있었습니다. 그때는 실제 프로젝트를 진행하면서 "자주 사용을 할까?"라는 의문이 들었었죠. 그런데 이번 과제를 진행하면서 실제로 타입 가드를 사용하게 되었고, 그 과정에서 타입 가드가 전용 문법이 있는 것이 아니라, 흔히 쓰는 제어문(예: if 문 등)을 활용해서도 오류를 최소화할 수 있다는 점을 알게 되었습니다.

 

그래서 이번 4주차 공유과제 주제로 타입 가드를 선택하게 되었습니다.

 

일반적으로 타입 가드에서 사용되는 키워드

1. typeof: 일반 타입 체킹

2. instanceof: 클래스 체킹

3. Array.isArray(): 배열 체킹

4. .type / in: 객체 속성 체킹

 

사실 위의 키워드들은 어떤 특별한 문법이 아니라, 모두 자바스크립트 코드인데요, 자바스크립트는 타입이 유연하기 때문에 타입 체킹 키워드들을 사용할 일이 없지만, 앞으로는 정말 많이 보게되실 아이들입니다.... !!!

 

일반 타입 가드 typeof

function numOrStr(a: number | string) {
    a.toFixed(1); 
}

 

위와 같은 코드처럼, 아무 생각 없이 작성했다가 에러가 뜨는 상황이 아주 빈번하게 발생하는데요, 타입스크립트 입장에서 toFixed는 number 전용 메소드인데 a가 number도 string도 될 수 있으니 에러가 날 것을 대비해 빨간줄로 우리에게 경고를 해주겠죠??

 

" string | number " 형식에 'toFixed' 속성이 없습니다. 'string' 형식에 'toFixed' 속성이 없습니다 " .. .. .. 아아 .. . . .. 

 

🥹 하지만 간단하게 typeof 키워드를 사용해서 조건 분기만 잘 해주면 해결할 수 있습니다.

function numOrStr(a: number | string) {
   if (typeof a === 'string') {
      a.split(',');
   }
   if (typeof a === 'number') {
      a.toFixed(1);
   }
}

 

앞으로 타입스크립트를 다룰 때마다  union 타입을 만나는 상황이 굉장히 많은데, 이때 타입 가드로 방어를 해주는 것이 중요합니다.

 

내장 클래스 타입을 보장할 수 있는 타입가드 instanceof

function func(value: number | string | Date | null) {
  if (typeof value === "number") {
    console.log(value.toFixed());
  } else if (typeof value === "string") {
    console.log(value.toUpperCase());
  } else if (value instanceof Date) {
    console.log(value.getTime());
  }
}

 

Date는 날짜 등을 포함하는 내장 클랙스 인데요, instanceof는 내장 클래스 또는 직접 만든 클래스에만 사용이 가능합니다. 따라서 우리가 직접 만든 타입과 함께 사용할 수 없습니다.

 

우리가 직접 만든 타입과 함께 사용할 수 있는 in

type Person = {
  name: string;
  age: number;
};

function func(value: number | string | Date | null | Person) {
  if (typeof value === "number") {
    console.log(value.toFixed());
  } else if (typeof value === "string") {
    console.log(value.toUpperCase());
  } else if (value instanceof Date) {
    console.log(value.getTime());
  } else if (value && "age" in value) {
    console.log(`${value.name}은 ${value.age}살 입니다`)
  }
}

 

age 문자가 객체에 key 속성으로서 쓰임을 검사하면 타입 가드를 할 수 있습니다.

 

이번 과제를 진행하면서 실제로 사용했던 부분을 가져와봤습니다.

 

      const loginData: PostLoginRequest = {
        username,
        password,
      };
      const response = await signIn(loginData);

 

로그인 함수(signIn)에 아이디와 비밀번호 값을 보내고 그에 대한 응답을 받습니다. 4주차 과제 문서에 있는 서버 명세서를 보면, 로그인에 대한 response 값은 다음과 같이 설정되어 있습니다.

response (success)
{
	"result" : {
		"token" : "abc"
	}
}

response (failed)
{
	"code" : "00"
}

 

 if ('result' in response) {
        localStorage.setItem('accessToken', response.result.token);
        console.log('로그인 요청결과:', response); // 요청 결과 콘솔로 출력
 ... ...
 .. 
 }


 

if ('result' in response)는 response 객체가 result라는 속성을 가지고 있는지 확인하는 조건문입니다. 만약 response 객체에 result 속성이 존재하면, TypeScript는 이 시점에서 response 객체가 result 속성을 가졌다고 판단하고, 안전하게 접근할 수 있습니다.

 

위에서 타입 가드는 어떠한 문법이 아니라, 여러 방법으로 나올 수 있는 타입을 좁혀주는 모든 것이라고 말씀드렸는데요, 다음과 같은 것들도 타입 가드로써 활용된 에시입니다.

비교구문

비교 조건과 그의 반대 조건을 합하면 모든 케이스를 커버합니다.

type Person = {
   play: () => void;
  sing: () => void;
  talk: () => void;
};

function playWithPerson(person: Person | undefined) {
  if (!person) {
    // person은 undefined로 추론됨.
    throw new Error('Person not found!');
  }
  // person은 Person으로 추론됨.
  person.play();
}

function talkWithPerson(person: Person | undefined | null) {
  if (!person) {
    // person은 undefined | null로 추론됨.
    throw new Error('Person not found!');
  }
  // person은 Person으로 추론됨.
  person.talk();
}

동등 연산자

function doSomething(left: string | number, right: string | boolean) {
  if (left === right) {
    // left와 right가 모두 string으로 추론됨.
    console.log(left.toLowerCase());
    console.log(right.toLowerCase());
    return;
  }
  // left는 string | number, right는 string | boolean으로 추론됨.
  console.log(`${left}`);
  console.log(`${right}`);
}

 

인터페이스 타입가드

인터페이스를 타입가드 하는 기법은 객체 타입 가드와 같습니다.

인터페이스에 식별할 수 있는 타입 속성을 기재해주고 .type 으로 검사해주면 됩니다!

interface Person {
   type: 'a';
   name: string;
   age: number;
}
interface Product {
   type: 'b';
   name: string;
   price: number;
}

function print(value: Person | Product) {
   if (value.type === 'a') {
      console.log(value.age);
   } else {
      console.log(value.price);
   }
}

 

번외: 할당

할당 구문도 아래 케이스처럼 일종의 타입가드로 사용하는 것입니다!

let value: string | number;

value = 3;
// value는 number로 추론됨.
value.toFixed(5);

value = 'abc';
// value는 string으로 추론됨.
value.split('').reverse().join('');

 

 

(참고문서

https://www.typescriptlang.org/docs/handbook/2/narrowing.html

https://ts.winterlood.com/92c2035a-49bc-4585-9e3d-43206ce92d59)