본문 바로가기
공부

[TS] 타입 좁히기

by jaeeedev 2023. 8. 13.

타입 좁히기( 타입스크립트 핸드북 )

원본 보기

function padLeft(padding: number | string, input: string) {
  return " ".repeat(padding) + input;

  // Argument of type 'string | number' is not assignable to parameter of type 'number'. Type 'string' is not assignable to type 'number'.

이 코드는 string|number 타입을 repeat의 인자로 넘기려고 해서 문제가 발생한다. repeat는 number 타입만 받을 수 있다.

typeof narrowing

function padLeft(padding: number | string, input: string) {
  if (typeof padding === "number") {
    return " ".repeat(padding) + input;
  }
  return padding + input;
}
``;

typeof 를 통해 들어온 padding의 타입을 검사한 후 타입이 number인 경우 repeat를 수행할 수 있다.

typeof 구문은 일반적인 자바스크립트와 같아 보이는데, 타입스크립트는 일반적인 자바스크립트 코드처럼 쉽게 작성할 수 있고 타입 안전성을 확보하는 방향을 목표로 한다.

typeof는 null을 반환하지 않는다.

function printAll(strs: string | string[] | null) {
  if (typeof strs === "object") {
    for (const s of strs) {
      // 'strs' is possibly 'null'.
      console.log(s);
    }
  } else if (typeof strs === "string") {
    console.log(strs);
  } else {
    // do nothing
  }
}

null의 타입도 object기 때문에 위의 예제에서 문제가 발생한다.

function printAll(strs: string | string[] | null) {
  if (strs && typeof strs === "object") {
    for (const s of strs) {
      console.log(s);
    }
  } else if (typeof strs === "string") {
    console.log(strs);
  }
}

Truthiness narrowing

Truthiness(boolean) 체크를 같이 곁들여서 문제를 해결할 수 있음

function printAll(strs: string | string[] | null) {
  // !!!!!!!!!!!!!!!!
  //  DON'T DO THIS!
  //   KEEP READING
  // !!!!!!!!!!!!!!!!
  if (strs) {
    if (typeof strs === "object") {
      for (const s of strs) {
        console.log(s);
      }
    } else if (typeof strs === "string") {
      console.log(strs);
    }
  }
}

이건 truthy check 부분이 모든 if문을 감싸는건데 이러면 빈문자열이 동작하지 않으니까 잘못됨 (ts문제라기보다는 자바스크립트에서 할 수 있는 실수...)

Equality narrowing

function printAll(strs: string | string[] | null) {
  if (strs !== null) {
    if (typeof strs === "object") {
      for (const s of strs) {

(parameter) strs: string[]
        console.log(s);
      }
    } else if (typeof strs === "string") {
      console.log(strs);

(parameter) strs: string
    }
  }
}

!== 같은 구문(equality)을 이용해 null을 검사할 수 있다.
== null로 검사하면 null 뿐만이 아니라 잠재적인 undefined도 추려낼 수 있다. == undefined도 마찬가지이다.

interface Container {
  value: number | null | undefined;
}

function multiplyValue(container: Container, factor: number) {
  // Remove both 'null' and 'undefined' from the type.
  if (container.value != null) {
    console.log(container.value); // number

(property) Container.value: number

    // Now we can safely multiply 'container.value'.
    container.value *= factor;
  }
}

느슨한 비교 != 를 통해 정의되지 않은 값을 배제했다. container.value는 number로 좁혀진다.

in operator narrowing

객체 프로퍼티의 타입을 좁히는 방법으로 in을 사용할 수 있다.

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

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

  return animal.fly();
}
type Fish = { swim: () => void };
type Bird = { fly: () => void };
type Human = { swim?: () => void; fly?: () => void };

function move(animal: Fish | Bird | Human) {
  if ("swim" in animal) {
    animal; // Fish | Human
  } else {
    animal; // Bird | Human
  }
}

선택적 속성은 양쪽 모두에 속할 수 있다.

instanceof narrowing

타입스크립트는 instanceof 에 해당하는 범위 내로 영역을 좁힌다. new 키워드로 생성할 수 있는 대부분의 값들에 사용할 수 있어 유용하다.

function logValue(x: Date | string) {
  if (x instanceof Date) {
    console.log(x.toUTCString());

(parameter) x: Date
  } else {
    console.log(x.toUpperCase());

(parameter) x: string
  }
}

분리된 유니온

원제는 discriminated union인데 해석이 애매하다..

interface Shape {
  kind: "circle" | "square";
  radius?: number;
  sideLength?: number;
}

이 방법은 circle과 square 유형을 함께 가지고 있다. circle 유형일 때만 radius가 들어올 것이고 square 유형일 때만 sideLength가 들어온다. 그래서 radius와 sideLength는 선택적으로 값이 들어오는 상태다.

function getArea(shape: Shape) {
  return Math.PI * shape.radius ** 2;
  // 'shape.radius' is possibly 'undefined'.
}

위의 이유로 'shape.radius' is possibly 'undefined'.  오류가 발생하게 된다. 하지만 kind 속성을 기반으로 radius나 sideLength가 있는지 여부를 판단할 방법이 없다. circle과 square의 타입을 구분하는 것이 좋다.

interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  sideLength: number;
}

type Shape = Circle | Square;

각각의 속성(radius, sideLength) 이 선택적이 아닌 필수적인 속성으로 선언되었다.

function getArea(shape: Shape) {
  if (shape.kind === "circle") {
    return Math.PI * shape.radius ** 2;

    // shape : Circle
  }
}

이제 유니온의 속성을 검증하여 타입스크립트가 타입을 좁힐 수 있다.

댓글