[ts] Narrowing / 타입 가드

Narrowing / 타입 가드

type을 check 하는 것은 type guard

type을 더 상세한 type으로 정제하는 과정은 Narrowing

function padLeft(padding: number | string, input: string) {
  // if로 타입 체크하는 것 자체는 type guard
	// 타입 체크 후 메서드를 호출하고 오퍼레이션을 하는 과정들은 narrowing
	if (typeof padding === "number") {
    return new Array(padding + 1).join(" ") + input;
  }
  return padding + input;
}

typeof

function padLeft(padding: number | string, input: string) {
	if (typeof padding === "number") {
    return new Array(padding + 1).join(" ") + input;
  }
  return padding + input;
}

instanceof

class Person {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

}

class Product {
    name: string;
    price: number;
    constructor(name: string, price: number) {
        this.name = name;
        this.price = price;
    }
}

function print1(value: Person | Product){
    if(value instanceof Person){
        console.log(value.age);
    }else {
        console.log(value.price);
    }
}


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

interface IProduct {
    name: string;
    price: number;
}


// 아래의 경우 에러가 나는데, 
// 컴파일 타임에 JS로 변환이 일어나면서, 인터페이스 선언부는 모두 제거가 된다.
// 그로 인해서 instanceof keyword 우측에는 클래스나, 생성자 함수가 와야한다.

function print2(value: IPerson | IProduct){
    if(value instanceof IPerson){
        console.log(value.age);
    }else {
        console.log(value.price);
    }
}


// 방법 1: 식별 가능한 유니온 타입
interface IPerson2 {
    type: 'a',
    name: string;
    age: number;
}

interface IProduct2 {
    type: 'b';
    name: string;
    price: number;
}
// 이 경우에도 타입 가드가 동작한다. 

function print3(value: IPerson2 | IProduct2){
    if(value.type === 'a'){
        console.log(value.age);
    }else {
        console.log(value.price);
    }
}

// 방법2: 타입을 구분 하는 함수 작성
function isPerson(x: Person| Product): x is Person {
    return (x as Person).age !== undefined;
}

function print4(value: IPerson2 | IProduct2){
    if(isPerson(value)){
        console.log(value.age);
    }else {
        console.log(value.price)
    }
}

The in operator narrowing

// 방법3:  js 'in' keyword 사용
function print5(value: IPerson2 | IProduct2){
    if('age' in value){
        console.log(value.age);
    }else {
        console.log(value.price)
    }
}

// optional이 있는 경우
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
  }
}

Type Predicate

return type에 Type Predicate를 사용할 수 있다. parameterName is Type 와 같은 방식으로 사용이 가능하고, parameterName은 실제 함수의 파라미터 이름과 같은 것을 사용해야한다.

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}
const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];
const underWater1: Fish[] = zoo.filter(isFish);
// or, equivalently
const underWater2: Fish[] = zoo.filter(isFish) as Fish[];
// The predicate may need repeating for more complex examples
const underWater3: Fish[] = zoo.filter((pet): pet is Fish => {
  if (pet.name === "sharkey") return false;
  return isFish(pet);
});

#타입스크립트/타입가드