TypeScript - Intersection & Type Narrowing

이소라·2022년 7월 9일
0

TypeScript

목록 보기
7/28

Intersection & Type Narrowing


Intersection Types

  • & 연산자를 사용하여 타입들을 결합한 타입
  • 주로 존재하는 객체 타입을 결합하는데 사용됨
type Colorful = {
  color: string;
}
type Circle = {
  radius: number;
}
 
type ColorfulCircle = Colorful & Circle;
  • ColorfulCircle 타입은 Colorful 타입의 color 속성과 Circle 타입의 radius 속성을 모두 가진 새로운 타입임
function draw(circle: ColorfulCircle) {
  console.log(`Color was ${circle.color}`);
  console.log(`Radius was ${circle.radius}`);
}
 
// okay
draw({ color: "blue", radius: 42 });
 
// error
draw({ color: "red" });
  • intersection type을 interface와 extends로 바꾸어 표현할 수 있음
interface Colorful {
  color: string;
}
interface Circle {
  radius: number;
}
 
interface ColorfulCircle extends Colorful, Circle {};
  • 객체 타입이 아닌 타입들도 결합할 수 있음
  • Union 타입들을 결합할 경우, 공통된 타입으로 됨
    • 하나의 타입이 공통될 경우, 그 타입이 됨
    • 두 개 이상의 타입이 공통될 경우, 공통된 타입들의 Union 타입이 됨
    • 공통된 타입이 없을 경우, never 타입이 됨
type Combinable = string | number;
type Numeric = number | boolean;
// Universal : number 타입
type Universal = Combinable & Numeric;
type Combinable = string | number;
type Numeric = string | number;
// Universal : string | number 타입
type Universal = Combinable & Numeric;
type Combinable = string | number;
type Numeric = null | boolean;
// Universal : never 타입
type Universal = Combinable & Numeric;


Narrowing

  • 정의된 타입에서 더 구체적인 타입으로 재정의하는 것을 narrowing이라고 함

typeof Type Guards

  • typeof 연산자는 해당 타입의 기본 정보를 반환함
    • 반환값 : "string", "nunber", "bigint", "boolean", "undefined", "object", "function"
function printAll(strs: string | string[] | null) {
  if (typeof strs === "object") {
    // Error: Object is possibly 'null'
    for (const s of strs) {
      console.log(s);
    }
  } else if (typeof strs === "string") {
    console.log(strs);
  } else {
    // do nothing
  }
}
  • typof null"object"이므로 string[] | null 타입으로만 좁혀짐

Truthiness narrowing

  • Boolean 함수나 !!(doble-Boolean negation)을 사용하여 값을 boolean으로 바꿀 수 있음
    • Boolean 함수를 사용한 경우, boolean 타입으로 추론됨
    • !!를 사용한 경우, narrow literal boolean 타입인 true 또는 false로 추론됨
Boolean("hello"); // type: boolean, value: true
!!"world"; // type: true,    value: true
Boolean(""); // type: boolean, value: false
!!""; // type: false,    value: false
  • null이나 undefined값을 거를 때 사용함
function multiplyAll(
  values: number[] | undefined,
  factor: number
): number[] | undefined {
  if (!values) {
    return values;
  } else {
    return values.map((x) => x * factor);
  }
}

Equality narrowing

  • switch문과 equality operator ===, !==, ==, !=를 사용하여 타입을 좁힐 수 있음

  • strict equality operator(===, !==) : 두 값의 값과 타입이 같은지를 비교함

function example(x: string | number, y: string | boolean) {
  if (x === y) {
    // x, y : string
    x.toUpperCase();
    y.toLowerCase();
  } else {
    // x : string | number
    console.log(x);
    // y : string | boolean
    console.log(y);
  }
}
  • x===y가 true가 되려면, x와 y의 타입과 값이 같아야하므로, x와 y의 타입은 공통 타입인 string이 되어야함

  • 특정한 literal 값이 아닌 경우에 대해서도 체크할 수 있음

function printAll(strs: string | string[] | null) {
  if (strs !== null) {
    // strs: string | string[]
    if (typeof strs === "object") {
      // strs: string[]
      for (const s of strs) {
        console.log(s);
      }
    } else if (typeof strs === "string") {
      // strs: string
      console.log(strs);
    }
  }
}
  • loose equality operator(==, !=) : 두 값을 비교할 때 암묵적 타입 변환을 통해 타입을 일치시킨 후 같은 값인지 비교함

  • undefined == null이 true이므로 ==null은 값이 null 또는 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) {
    // container.value : number
    console.log(container.value);
    container.value *= factor;
  }
}

in operator narrowing

  • in operator: 객체 안에 해당 이름의 속성이 존재하는지를 확인함
    • 해당 이름의 속성이 존재하거나 optional일 때 in operator를 포함한 식이 true가 됨
    • 해당 이름의 속성이 존재하지 않거나 optional일 때 in operator를 포함한 식이 false가 됨
type Fish = { swim: () => void };
type Bird = { fly: () => void };
 
function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    // animal : Fish
    return animal.swim();
  }
  // animal: Bird
  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;
      
(parameter) animal: Fish | Human
  } else {
    animal;
      
(parameter) animal: Bird | Human
  }
}

instanceof narrowing

  • A instanceof B: A가 B의 인스턴스인지 확인함, A가 B의 프로토타입 체인에 존재하는지 확인함
function logValue(x: Date | string) {
  if (x instanceof Date) {
    // x: Date
    console.log(x.toUTCString());
  } else {
    // x: string
    console.log(x.toUpperCase());
  }
}

Assignments

  • 할당한 값을 통해서 타입을 추론함
let x = Math.random() < 0.5 ? 10 : "hello world!";
// x: string | number

x = 1;
console.log(x);
// x: number;     

x = "goodbye!"; 
console.log(x);
// x: string
  • 정의된 타입에 대한 값들만 할당할 수 있음
let x = Math.random() < 0.5 ? 10 : "hello world!";
// x: string | number

x = true;
// Error: Type 'boolean' is not assignable to type 'string | number'
console.log(x);

Discriminated unions

  • discriminated unions : union의 모든 타입이 literal type의 공통 속성을 가질 때 dicriminated unions라고 함
interface Circle {
  kind: "circle";
  radius: number;
}
 
interface Square {
  kind: "square";
  sideLength: number;
}
 
type Shape = Circle | Square;

function getArea(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      // shape: Circle
      return Math.PI * shape.radius ** 2;
    case "square":
      // shape: Square
      return shape.sideLength ** 2;
  }
}

Exhaustiveness check

  • never 타임은 모든 타입에 할당 가능하지만, never타입에는 never타입만 할당 가능함
  • switch 문과 never 타입을 통해서 exhaustive 확인이 가능함
type Shape = Circle | Square;
 
function getArea(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.sideLength ** 2;
    default:
      // shape: never
      const _exhaustiveCheck: never = shape;
      return _exhaustiveCheck;
  }
}
  • 만약 Shape union 타입 내에 타입이 더 존재한다면 에러가 발생함
interface Triangle {
  kind: "triangle";
  sideLength: number;
}
 
type Shape = Circle | Square | Triangle;
 
function getArea(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.sideLength ** 2;
    default:
      // Error: Type 'Triangle' is not assignable to type 'never'
      const _exhaustiveCheck: never = shape;
      return _exhaustiveCheck;
  }
}

참고링크

0개의 댓글