[TS] 타입 좁히기(Narrowing)

정재은·2023년 8월 24일
0

TypeScript

목록 보기
13/14
post-thumbnail
post-custom-banner

🔷 타입 좁히기

유니온 타입을 통해 여러 타입을 가진 형태를 표현할 수 있었다
하지만 특정 타입을 가질때만 로직을 실행해야 하는 경우에 유니온 타입은 도움이 되지 못한다

이렇게 넓은 타입을 더 좁은 타입으로 재정의 하는 것을 타입 좁히기 라고 한다

주로 명확하지 않은 타입이 있을 때 사용하며 말그대로 타입을 좁혀나가는 과정이다






🔷 타입 가드 (Type Guard)

특정한 스코프 내에서 타입 좁히기를 유발하는 표현타입 가드(Type Guard)라고 한다

특정 조건이 만족될 때에만 코드를 실행하거나 같은 코드를 여러번 실행하는 식으로
순차적 실행을 벗어난 실행을 가능하게 한다

	if, else if, else
	while, for 
    switch, case 
    break, continue 
    return


1. Typeof

문자열, 숫자, 불리언과 같은 원시 값 처리할 때 주로 사용한다

조건문과 typeof 키워드를 사용하여 원시값을 체크할 수 있다

function printTriple(value: number | string) {
  // 조건문으로 value의 타입이 string인지 판별
  if (typeof value == 'string') {
    return value.repeat(3);   // value: string
  }
  return value * 3; // value: number
}

console.log(printTriple('hi'));  // 'hihihi'



2. Truthiness

null, undefined, falsy 값을 좁히거나 제거할 수 있다

// EX1
const thing = document.getElementById('idk');

if (thing) {
  thing;  // thing: HTMLElement
} else {
  thing;  // thing: null
}
// EX2
const printLetters = (word?: string) => {
  if (word) {
    for (let char of word) {  // word: string
      console.log(char);
    }
  } else {
    // word : string | undefined (truthy인지 falsy인지 확인만 하기 때문에)
    console.log('YOU DID NOT PASS IN A WORD!');
  }
}

printLetters('HI');  // H  // I
printLetters();  // YOU DID NOT PASS IN A WORD!



3. Equality 동등 좁히기

비교 연산자 = 를 사용하여 값을 좁혀나간다

이중등호 == 를 사용할 경우 "3"과 3을 같다고 인식해 타입체킹이 제대로 일어나지 않는다
반드시 삼중등호 ===를 사용하여 타입까지 판별할 것!

function equality(x: string | number, y: string | boolean) {
  if (x === y) {
    console.log(x.toUpperCase());  // x: string,  y: string
  }
}

equality(3, '3');
equality('abc', 'abc');  // ABC



4. In

A in B 👉🏻 A(프로퍼티)가 B에 존재하는지 판별한다

in 연산자를 활용하면 특정 프로퍼티인터페이스나 타입별칭 내에 있는지 판별할 수 있다

✏️ in을 주로 사용하는 경우
① typeof를 사용할 수 없는 경우
② 인터페이스나 타입별칭을 활용해 객체로 작업하는 경우

// EX1)
const pet = { name: 'Kitty', age: 20 };

console.log('name' in pet);  // true
console.log('age' in pet);  // true
console.log('legs' in pet);  // false
// EX2)
interface TVShow {
  title: string;
  episodes: number;
  episodeDuration: number;
}

interface Movie {
  title: string;
  duration: number;
}

function getRuntime(media: TVShow | Movie) {
  let runtime;
  if ('episodes' in media) {
    // media: TVShow
    runtime = media.episodes * media.episodeDuration;
  } else {
    // media: Movie
    runtime = media.duration;
  }
  console.log(`${media.title} : ${runtime}`);
}

getRuntime({ title: '더문', duration: 129 });  // 더문 : 129분
getRuntime({ title: '더글로리', episodes: 16, episodeDuration: 60 });  // 더글로리 : 960분



5. Instanceof

A instanceof B 👉🏻 A(객체)가 B(클래스)에 존재하는지 판별한다

instanceof 연산자를 활용하면 객체(A)어떤 클래스(B)인지, 어떤 클래스를 상속받았는지(B)를 판별할 수 있다

✏️ instanceof를 주로 사용하는 경우
 ① typeof를 사용할 수 없는 경우
 ② new 키워드를 사용해 초기화한 클래스 등으로 작업하는 경우

// EX1)
function printFullDate(date: string | Date) {
  if (date instanceof Date) {
    // date: Date
    console.log(`Date : ${date.toUTCString()}`);
  } else {
    // date: string
    console.log(`String : ${new Date(date).toUTCString()}`);
  }
}

printFullDate(new Date());  // Date : Thu, 24 Aug 2023 08:42:18 GMT
printFullDate('Thu, 24 Aug 2023 GMT');  // String : Thu, 24 Aug 2023 00:00:00 GMT
// EX2)
class User {
  constructor(public username: string) {}
}

class Company {
  constructor(public companyname: string) {}
}

function printName(person: User | Company) {
  if (person instanceof User) {
    // person: User
    console.log(`UserName : ${person.username}`);
  } else {
    // person: Company
    console.log(`CompanyName : ${person.companyname}`);
  }
}
const user = new User('jan');
printName(user);  // UserName : jan

const company = new Company('네이버');
printName(company);  // CompanyName : 네이버



6. 타입명제 (Predicate)

parameterName is Type   parameterName as Type

타입스크립트에만 있는 기능으로
함수(isXXX)를 직접 정의하여 TypeScript에게 XXX의 타입이 무엇인지를 알려준다

타입명제는 해당 함수의 타입으로 작성되어야 한다

interface Cat {
  name: string;
  age: number;
}
interface Dog {
  name: string;
  leg: number;
}


// isXXX 형태의 함수를 직접 정의
// ↳ Cat의 속성이 있다면 true, 없다면 false를 반환
function isCat(animal: Cat | Dog): animal is Cat {
  //                              (animal is Cat) → true를 반환한다면 Cat 타입이다
  return (animal as Cat).age !== undefined;
  //     (animal as Cat) → animal이 Cat임을 단언, Cat의 속성을 사용
}


function makeNoise(animal: Cat | Dog): string {
  if (isCat(animal)) {
    animal;   // animal: Cat
    return 'Meow~~';
  } else {
    animal;   // animal: Dog
    return 'BowWow!';
  }
}

const cathy: Cat = { name: 'Cathy', age: 4 };
console.log(makeNoise(cathy));   // Meow~~

const max: Dog = { name: 'Max', legs: 1 };
console.log(makeNoise(max));   // BowWow!



7. 판별 유니온

모든 타입공통된 프로퍼티판별자를 추가하여 타입을 판별한다

① 공통된 프로퍼티를 공유하는 여러 타입을 생성하고
② 해당 프로퍼티에 리터럴 값할당하여 사용한다

공통 프로퍼티는 하나만 있어도 가능하다

// 공통 프로퍼티 : kind
interface Rooster {
  name: string;
  weight: number;
  age: number;
  kind: 'rooster';
}

interface Cow {
  name: string;
  weight: number;
  age: number;
  kind: 'cow';
}

interface Pig {
  name: string;
  weight: number;
  age: number;
  kind: 'pig';
}

type FarmAnimal = Pig | Rooster | Cow;

function getFarmAnimalSound(animal: FarmAnimal) {
  switch (animal.kind) {
    case 'rooster':
      animal; // animal: Rooster
      return '꼬끼오';

    case 'cow':
      animal; // animal: Cow
      return '음메';

    case 'pig':
      animal; // animal: Pig
      return '꿀꿀';
  }
}

const stevie: Rooster = {
  name: 'Stevie',
  weight: 3,
  age: 2,
  kind: 'rooster',
};

console.log(getFarmAnimalSound(stevie)); // 꼬끼오

자바스크립트 개발자를 위한 타입스크립트

profile
프론트엔드
post-custom-banner

0개의 댓글