๋ณธ๋ฌธ์œผ๋กœ ๋ฐ”๋กœ๊ฐ€๊ธฐ

ํƒ€์ž… ๊ฐ€๋“œ(Type Guard)

๋ฐฐ์šธ ๊ฐœ๋…

  • intersection Type
  • union Type
  • Type Guard

 

1. ๋ฌธ์ œ ๋ฐœ์ƒ

interface Animal {
  eat: () => void;
  sleep: () => void;
}
class Dog implements Animal {
  eat() {}
  sleep() {}
}
class Cat implements Animal {
  eat() {}
  sleep() {}
}

Dogํด๋ž˜์Šค์— bark()๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์€๋ฐ?

  • ๊ทธ๋Ÿฌ๊ธฐ ์œ„ํ•ด์„ , Animal์ธํ„ฐํŽ˜์ด์Šค์— bark()๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผํ•˜๊ณ , Animal์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” Catํด๋ž˜์Šค๋„ bark()๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผํ•˜๋Š” ๋น„ํšจ์œจ์ด ๋ฐœ์ƒํ•œ๋‹ค.
  • ํ•ด๋‹น ํƒ€์ž…์„ ์“ฐ๋Š” ๋ชจ๋“  ์ฝ”๋“œ์— ๋ณ€๊ฒฝ์„ ๊ฐ€ํ•˜์ง€ ์•Š๊ณ , ํŠน์ • ์ฝ”๋“œ๋งŒ ์ž์œ ๋กญ๊ฒŒ ํƒ€์ž…์„ ํ™•์žฅํ•˜๋Š” ๋ฐฉ๋ฒ• ์—†์„๊นŒ?

 

2. Intersection Type

  • A & B: A ํƒ€์ž…์ด๋ฉด์„œ B ํƒ€์ž…
type Human = {
  think: () => void,
};

type Developer = {
  work: () => void,
};

const junha: Human & Developer = {
  think() {
    console.log(`I'm thinking`);
  },
  work() {
    console.log(`I'm working`);
  },
};

์–ธ์ œ ์‚ฌ์šฉํ•ด?

  • Intersection Type์€ ๊ธฐ์กด ํƒ€์ž…์„ ๋Œ€์ฒดํ•˜์ง€ ์•Š์œผ๋ฉด์„œ, ์ƒˆ๋กœ์šด ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉ
type Human = {
  think: () => void;
};
type Developer = {
  work: () => void;
};
type Rich = {
  flex: () => void;
};

const junha: Human & Developer & Rich = {
  think() {
    console.log(`I'm thinking`);
  },
  work() {
    console.log(`I'm working`);
  },
  flex() {
    console.log(`I'm flexing`);
  },
};
  • ๊ธฐ์กด์˜ Human & Developer ํƒ€์ž…์˜ junha์— Rich ํƒ€์ž…์„ ์ถ”๊ฐ€ํ•ด ๊ธฐ์กด ํƒ€์ž…์€ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฃผ์˜!! ๊ฐ ํƒ€์ž…์— ๊ฒน์น˜๋Š” ํ•„๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

 

  • intersection ํ•˜๋ ค๋Š” ๋‘ ํƒ€์ž…์˜ ํ•„๋“œ๊ฐ€ ๋‹ค๋ฅด๋‹ค๋ฉด ์˜ค๋ฅ˜๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค.
//  develop ๋ฉ”์„œ๋“œ ์„ ์–ธ, ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์—†๊ณ , ๋ฐ˜ํ™˜ ํƒ€์ž…์€ void์ด๋‹ค
type Developer = {
  develop: () => void;
};

type Designer = {
  design: () => void;
};

const ๊ฐœ์ž์ด๋„ˆ: Developer & Designer = {
  develop() {
    console.log("I'm programming");
  },
  design() {
    console.log("I'm designing");
  },
};

๊ฐœ์ž์ด๋„ˆ.develop(); // I'm programming ์ถœ๋ ฅ
๊ฐœ์ž์ด๋„ˆ.design(); // I'm designing ์ถœ๋ ฅ

 

3. Union Type

  • A | B: A ํƒ€์ž…, B ํƒ€์ž… ๋‘˜ ์ค‘ ํ•˜๋‚˜
let one: string | number;
one = '1';
one = 1;

์–ธ์ œ ์‚ฌ์šฉํ•ด?

  • ์—ฌ๋Ÿฌ ํƒ€์ž… ์ค‘ ํ•˜๋‚˜๊ฐ€ ์˜ฌ ๊ฒƒ์ด๋ผ๊ณ  ๊ฐ€์ •ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
  • ํ•˜์ง€๋งŒ ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์œ ๋‹ˆ์˜จ ํƒ€์ž…์„ ํ™•์žฅํ•˜์ง€ ๋ชปํ•œ๋‹ค.
  • ์ด๋•Œ๋Š” type ํ‚ค์›Œ๋“œ์™€ &(Intersection Type)์„ ์‚ฌ์šฉํ•ด ํ™•์žฅํ•œ๋‹ค.
type A = string | number;

interface StrOrNum extends A {
  a: string;
}
// error An interface can only extend
// an object type or intersection of object types with statically known members.


type StrOrNum = {
  a: string;
} & (string | number);
// Ok

์ฃผ์˜!! Union Type์€ ๋™์‹œ์— ์—ฌ๋Ÿฌ ํƒ€์ž…์ด ๋  ์ˆ˜ ์—†๋‹ค.

type Human = {
  think: () => void;
};
type Dog = {
  bark: () => void;
};

declare function getType(): Human | Dog;

const junha = getType();
junha.think(); // Property 'think' does not exist on type 'Dog'.
junha.bark(); // Property 'bark' does not exist on type 'Human'.
  • junha์˜ ํƒ€์ž…์€ ์œ ๋‹ˆ์–ธ ํƒ€์ž…์œผ๋กœ ์„ ์–ธ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, Human์ธ์ง€ Dog์ธ์ง€ ํ™•์‹ ํ•  ์ˆ˜ ์—†๋‹ค.
  • ๋”ฐ๋ผ์„œ think, bark ๋‘˜ ์ค‘ ์–ด๋А ๋ฉ”์„œ๋“œ๋„ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•œ๋‹ค.
  • junha.think() ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ junha๊ฐ€ Human์ด๋ผ๋ฉด ๊ดœ์ฐฎ์ง€๋งŒ Dog๋ผ๋ฉด think๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ปดํŒŒ์ผ ๋‹จ๊ณ„์—์„œ ์—๋Ÿฌ๊ฐ€ ๋‚˜์˜จ๋‹ค.
  • ์ด ์—๋Ÿฌ๋Š” ํƒ€์ž… ๊ฐ€๋“œ๋ฅผ ํ†ตํ•ด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

 

4. Type Guard

1) ํƒ€์ž… ๊ฐ€๋“œ๋ž€?

  • ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…์„ ์•Œ ์ˆ˜ ์—†๊ฑฐ๋‚˜, ๋  ์ˆ˜ ์žˆ๋Š” ํƒ€์ž…์ด ์—ฌ๋Ÿฌ ๊ฐœ๋ผ๊ณ  ๊ฐ€์ •ํ•  ๋•Œ, ์กฐ๊ฑด๋ฌธ์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…์„ ์ขํ˜€๋‚˜๊ฐ€๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค.
  • ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…์— ๋”ฐ๋ผ ๋Œ€์‘ํ•˜์—ฌ ์—๋Ÿฌ๋ฅผ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ํƒ€์ž…์„ ํ†ตํ•ด ‘๊ฐ€๋“œ’ํ•˜๋Š” ์ฝ”๋“œ, ๋ฐฉ์–ด์ ์ธ ์ฝ”๋“œ๋ฅผ ์งค ์ˆ˜ ์žˆ๋‹ค.

2) ํƒ€์ž… ๊ฐ€๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ์œ ๋‹ˆ์–ธ ํƒ€์ž…์„ ์ œ๋Œ€๋กœ ์‚ฌ์šฉํ•ด๋ณด์ž

type Human = {
  think: () => void;
};
type Dog = {
  tail: string;
  bark: () => void;
};

declare function getType(): Human | Dog;

const junha = getType();

// here is the clue
if ('tail' in junha) {
  junha.bark();
} else {
  junha.think();
}

3) What happend?!

  • ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์—๊ฒŒ ํƒ€์ž…์„ ์ถ”๋ก ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‹จ์„œ๋ฅผ ์ฃผ์—ˆ๋‹ค!
  • ์ด๋ ‡๊ฒŒ ํƒ€์ž…์„ ๊ตฌ๋ณ„ํ•  ์ˆ˜ ์žˆ๋Š” ๋‹จ์„œ๊ฐ€ ์žˆ๋Š” ์œ ๋‹ˆ์˜จ ํƒ€์ž…์€ ๊ตฌ๋ณ„๋œ ์œ ๋‹ˆ์˜จ๋ผ๊ณ (ํƒœ๊ทธ๋œ ์œ ๋‹ˆ์˜จ, ์„œ๋กœ์†Œ ์œ ๋‹ˆ์˜จ)ํ•œ๋‹ค.

4) ์‹ค๋ฌด์—์„œ ์ž์ฃผ ์“ฐ๋Š” ๊ตฌ๋ณ„๋œ ์œ ๋‹ˆ์˜จ๊ณผ ํƒ€์ž… ๊ฐ€๋“œ

type SuccessResponse = {
  status: true;
  data: any;
};
type FailureResponse = {
  status: false;
  error: Error;
};

type CustomResponse = SuccessResponse | FailureResponse;

declare function getData(): CustomResponse;

const response: CustomResponse = getData();
if (response.status) {
  console.log(response.data);
} else if (response.status === false) {
  console.log(response.error);
}
  • ์„œ๋ฒ„์—์„œ ์˜ค๋Š” ์‘๋‹ต ๋˜๋Š” ํ•จ์ˆ˜์˜ ๊ฒฐ๊ณผ๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐˆ๋ž˜๋กœ ๋‚˜๋‰  ๋•Œ ๊ตฌ๋ณ„๋œ ์œ ๋‹ˆ์˜จ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • ํƒ€์ž…์˜ ๋‹จ์„œ๋ฅผ ํ† ๋Œ€๋กœ ํƒ€์ž… ๊ฐ€๋“œ๋ฅผํ•˜๊ณ , ์‘๋‹ต์˜ ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์ž‘์—…์„ ์‹คํ–‰์‹œ์ผœ์ค€๋‹ค.

5) ๋‹ค์–‘ํ•œ ์—ฐ์‚ฐ์ž๋ฅผ ํ†ตํ•œ ํƒ€์ž… ๊ฐ€๋“œ

01. instanceof

  • ํด๋ž˜์Šค๋„ ํƒ€์ž…์ด๋‹ค. ๊ฐ์ฒด๊ฐ€ ์–ด๋–ค ํด๋ž˜์Šค์˜ ๊ฐ์ฒด์ธ์ง€ ๊ตฌ๋ณ„ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์—ฐ์‚ฐ์ž
  • ์ธ์Šคํ„ด์Šค instanceof ํด๋ž˜์Šค์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•œ๋‹ค.
class Developer {
  develop() {
    console.log(`I'm working`);
  }
}
class Designer {
  design() {
    console.log(`I'm working`);
  }
}
const work = (worker: Developer | Designer) => {
  if (worker instanceof Designer) {
    worker.design();
  } else if (worker instanceof Developer) {
    worker.develop();
  }
};

02. typeof

  • ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์—ฐ์‚ฐ์ž
  • typeof ๋ฐ์ดํ„ฐ === 'string' ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉ
  • typeof ๋ฐ์ดํ„ฐ === 'undefined' ์ฒ˜๋Ÿผ undefined ์ฒดํฌ๋„ ๊ฐ€๋Šฅ
  • ์ฐธ๊ณ ๋กœ ๋ฐ์ดํ„ฐ == 'null'๊ณผ ๊ฐ™์ด ์“ฐ๋ฉด null, undefined ๋‘˜ ๋‹ค ์ฒดํฌ
const add = (arg?: number) => {
  if (typeof arg === 'undefined') {
    return 0;
  }
  return arg + arg;
};

03. in

  • key in obj: obj.key์˜ ์กด์žฌ์—ฌ๋ถ€ ๋ฆฌํ„ด
type Human = {
  think: () => void;
};
type Dog = {
  tail: string;
  bark: () => void;
};

declare function getType(): Human | Dog;

const junha = getType();
if ('tail' in junha) {
  junha.bark();
} else {
  junha.think();
}

04. literal type guard

  • ๋ฆฌํ„ฐ๋Ÿด ํƒ€์ž…: ํŠน์ • ํƒ€์ž…์˜ ํ•˜์œ„ ํƒ€์ž…. ๊ตฌ์ฒด์ ์ธ ํƒ€์ž…. string ํƒ€์ž…์˜ ๋ฆฌํ„ฐ๋Ÿด ํƒ€์ž…์€ ‘cat’, ‘dog’
  • ๋ฆฌํ„ฐ๋Ÿด ํƒ€์ž…์€ ๋™๋“ฑ(==), ์ผ์น˜(===) ์—ฐ์‚ฐ์ž ๋˜๋Š” switch๋กœ ํƒ€์ž… ๊ฐ€๋“œ ๊ฐ€๋Šฅ
  • ์ฐธ๊ณ ๋กœ ์˜ˆ์ œ์ฒ˜๋Ÿผ ํ•˜๋‚˜์˜ value ๊ฐ’์— ๋”ฐ๋ผ ์กฐ๊ฑด๋ฌธ์„ ์ž‘์„ฑํ•˜๋Š” ๊ฒฝ์šฐ, ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ switch์˜ ์—ฐ์‚ฐ ๊ฒฐ๊ณผ๊ฐ€ if-else๋ณด๋‹ค ๋” ๋น ๋ฅด๋ฏ€๋กœ ๋˜๋„๋ก switch๋ฅผ ์“ฐ๋Š” ๊ฑธ ๊ถŒ์žฅํ•ด ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์กฐ๊ฑด๋ฌธ์˜ ๊ฐœ์ˆ˜๊ฐ€ ์ ์œผ๋ฉด ํฐ ์ฐจ์ด๋Š” ์—†์ง€๋งŒ, ์กฐ๊ฑด๋ฌธ์˜ ๊ฐœ์ˆ˜๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก switch์˜ ์„ฑ๋Šฅ์ด ๋” ๋น ๋ฅด๋‹ค.
type Action = 'click' | 'hover' | 'scroll';

const doPhotoAction = (action: Action) => {
  switch (action) {
    case 'click':
      showPhoto();
      break;
    case 'hover':
      zoomInPhoto();
      break;
    case 'scroll':
      loadPhotoMore();
      break;
  }
};

const doPhotoAction2 = (action: Action) => {
  if (action === 'click') {
    showPhoto();
  } else if (action === 'hover') {
    zoomInPhoto();
  } else if (action === 'scroll') {
    loadPhotoMore();
  }
};

05. ์‚ฌ์šฉ์ž ์ •์˜ ํ•จ์ˆ˜

import is from '@sindresorhus/is';

const getString = (arg?: string) => {
  if (is.nullOrUndefined(arg)) {
    return '';
  }
  if (is.emptyStringOrWhitespace(arg)) {
    return '';
  }
  return arg;
};
  • type guard์— ์œ ์šฉํ•œ ์˜คํ”ˆ์†Œ์Šค ์ค‘ sindresorhus/is๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ€
    ๋…์„ฑ ์žˆ๊ฒŒ ํƒ€์ž…์„ ์ฒดํฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • Yarn add @sindersorhus/is, npm install @sindersorhus/is
๋ฐ˜์‘ํ˜•