본문으로 바로가기

type guard가 왜 필요한가

 

아래의 경우 유니언 타입으로 잡아주었기 때문에 공통적인 프로퍼티만 사용할 수 있습니다.

만약 intersection을 사용하면 정의 자체를 name, age, skill을 전부 넣어줘야 하니 의도한 방식이 아닙니다.

interface Developer {
  name: string;
  skill: string;
}

interface Person {
  name: string;
  age: number;
}

function introduceSelf(): Developer | Person {
  return {
    name: "darren",
    age: 23,
  };
}

// Developer | Person 이므로 공통 특성인 name만 사용할 수 있음.
const daren = introduceSelf();
console.log(daren.age); // Error!

 

이런 동작 방식을 해결하기 위해서는 type assersion을 사용해서 아래와 같이 작성해주면 사용할 수 있기는 합니다.

그러나 이런 방식을 verbose 합니다. 계속 타입을 붙여줘야 하는 것도 가독성적으로도, DX적으로도 나쁩니다.

const darren = introduceSelf();

if (darren as Person) {
  console.log((darren as Person).age);
}

 

이러한 점을 개선하는 방식이 type guard입니다. 특정 인자가 어떤 타입에 속하는지 확인하는 함수를 작성하는 방식입니다.

type guard에는 여러가지 방식이 있지만 여기서는 아름답게 'is'를 사용하여 타입 명제를 만들고 이를 활용해 type guard를 할 수 있.

// 타입 가능 정의 (is를 사용)
function isDeveloper(target: Developer | Person): target is Developer {
  return (target as Developer).skill !== undefined;
}

// type guard를 한 번 거친 후에는 타입에 따른 프로퍼티를 사용 가능
if (isDeveloper(darren)) {
  console.log(darren.skill);
} else {
  console.log(darren.age);
}

 

type Narrowing의 방법

 

1. typeof

 

typeof를 사용한 type narrowing은 쉽고 빈번하게 사용된다. 그러나 typescript스럽다고는 할 수 없다.

js에서는 짜증나게도 typeof null => object를 반환한다.

그래서 typeof === object 만 걸러낸다면 null이 들어올 수도 있게 된다.

ts에서는 이러한 점 때문에 Object is possibly 'null'. 경고를 종종 보낸다.

typeof를 통해서 type narrowing을 하는 것은 좋지만 다른 방법도 있음을 알아두자.

 

 

2.  falsy / truthy한 값을 if/switch/equality check하기

 

워낙 많이 쓰는 방법이니 설명은 생략.

다만, Boolean(null), Boolean(NaN), Boolean('')가 false를 반환하기 때문에 typeof보다 훨씬 좋다는 점이다.

이러한 기법을 'Double-Boolean negation' 이라고 부른다.

 

 

3. in operator를 통해서 프로퍼티 유무를 통해 타입 체킹하기

 

보다시피 obj의 특정한 프로퍼티가 존재하는지 체킹한다.

const me = { name: 'darren' }
console.log('name' in me) // true

 

boolean값을 반환하므로 다음과 같이 type narrowing을 할 수 있을 것이다.

type Fish = { swim: () => void }
type Bird = { fly: () => void }

function move (animal: Fish | Bird) {
  if ('fly' in animal) {
    return animal.fly()
  }
  return animal.swim()
}

 

 

4. instanceOf 체킹하기

 

설명 생략. 간단히 말하자면 특정 클래스로 인해 생성된 인스턴스인지 체크한다는 것이다. boolean 값을 반환하므로 당연히 type guard가 될 것이다.

 

5. const assertion 활용하기

function getShapes() {
  let result = [
    { kind: "circle", radius: 100 },
    { kind: "square", sideLength: 50 },
  ] as const;
  return result;
}

for (const shape of getShapes()) {
  // Narrows perfectly!
  if (shape.kind === "circle") {
    console.log("Circle radius", shape.radius);
  } else {
    console.log("Square side length", shape.sideLength);
  }
}

 

6. Type Predicates(타입 명제)를 사용하기

 

후술하도록하겠다.

 

 

Type Predicates

 

아래 코드는 내가 회사에서 짠 코드를 일부 변형한 것이다. 

명제를 세워놓고, 해당 명제를 만족할 조건을 반환하면, type guard를 실현할 수 있다.

export type EntityType = IDocument | IFolder

export const documentGuard = (item: EntityType): item is IDocument => {
  return item.itemType === 'document' // 명제를 만족할 조건(boolean 값을 반환해야 함)
}

export const folderGuard = (item: EntityType): item is IFolder => {
  return item.itemType === 'folder' // 명제를 만족할 조건(boolean 값을 반환해야 함)
}

 

조건을 만족하는 경우 true를 반환하고, 통과한 값은 명제를 만족하므로 특정 type을 가지게 된다. 매우 좋음~~

if (documentGuard(focusedItem)) {
  return setDocumentOptionByDocumentId(dispatch)(
    documentOptions, 
    focusedItem.documentId, 
    true 
  )
}

 

 

 

 

 

 

 

 

 

 


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체