[TS] 기초(4)

jiseong·2021년 11월 27일
0

T I Learned

목록 보기
142/291
post-custom-banner

Intersection Type

  • 여러 타입을 모두 만족하는 하나의 타입을 지정해주고 싶을 때 사용하며 &(앰퍼샌드) 연산자를 사용하여 나타낸다.
type Toy = { 
    color: string ;
};

type Car = { 
    name: string; 
};

위와 같은 타입이 있다고 가정했을 때, 새로운 장난감차 타입을 표현하고 싶을 때 단순하게 해당 타입들의 모든 프로퍼티를 정의하는 방식으로도 새로운 타입을 지정해줄 수 있다.

type ToyCar = { 
    color: string; 
    name: string; 
};

하지만 추후에 Car나 Toy 타입에 프로퍼티의 추가/삭제 등의 수정이 일어나면 해당 타입을 사용하는 모든 타입들에 대해서 수정을 해줘야한다.

이러한 번거로움 해결 할 때 유용한 방식이 Intersection Type을 이용하는 것이다. Intersection Type방식을 이용한다면 다음과 같이 나타낼 수 있다.

type ToyCar = Toy & Car;

Union Type

  • 하나 이상의 타입을 지정하고 싶을 때 사용하며 |(바) 연산자를 사용하여 나타낸다.
let one: string | number;
one = '1';
one = 1;

예시 1)

function padLeft(value: string, padding: any) {
  if (typeof padding === "number") {
    return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
    return padding + value;
  }
  throw new Error(`Expected string or number, got '${typeof padding}'.`);
}

padLeft("Hello world", 4); // returns "    Hello world"

위와 같이 padding이라는 변수에 any타입을 지정하게 되면 우리가 의도했던 number 타입 또는 string타입 이외의 값을 할당하더라도 컴파일 단계에서는 에러가 뜨지 않는다.

이러한 상황에서 Union Type을 통해 원하고자 했던 타입들을 지정해주면 컴파일 타임에서 오류로 표시해주기 때문에 유용하다.

function padLeft(value: string, padding: string | number) {
  // ...
}

let indentedString = padLeft("Hello world", true);
// [Errors in code]
// Argument of type 'boolean' is not assignable to parameter of type 'string | number'

예시 2)

type alphanumeric = string | number;

const add = (a: alphanumeric, b: number): number => {
  return a + b;
}

위와 같이 유니온타입으로 숫자와 문자열만 지정할 수 있는 alphanumeric 타입을 지정해주었을 때 해당 코드는 컴파일 단계에서 오류를 발생시킨다.
Operator '+' cannot be applied to types 'alphanumeric' and 'number'

이유는 타입스크립트 컴파일러는 함수를 호출하기 전까지 a변수에 어느 타입이 올지 알 수 없기 때문에 어느 타입이 와도 오류가 나지 않는 방향으로 타입을 추론한다.

그래서 보통은 이러한 오류를 나지 않게끔 Type Guard를 사용한다.

Type Guard

타입 가드는 특정 범위 안에서 런타임 타입 체크를 수행하는 표현식으로 컴파일러가 타입을 예측할 수 있도록 작성하는 것을 의미한다.

여기서 런타임 타입 체크의 의미는 타입스크립트는 실제로 런타임시에 타입을 제거하는데 타입가드를 사용한다면 특정 범위 안에서 타입을 확실시한다는 것으로 이해했다.

typeof

변수 또는 프로퍼티 타입을 참조하고 싶을 때 사용하는 연산자

const add = (arg?: number) => {
  
    // Type Guard
    if(typeof arg === 'undefined') {
        return 0;
    }
    return arg + arg;
}

console.log(add(5)); // 10
console.log(add()); // 0

typeof 연산자를 통해 arg변수의 타입을 비교하여 타입가드를 할 수 있다.

typeof 연산자는 'string' / 'number' / 'bigint' / 'boolean' / 'symbol' / 'undefined' / 'object' / 'function' 타입만 비교 할 수 있다.

instanceof

객체가 어떤 클래스의 객체인지 구별할 때 사용하는 연산자

class Developer {
    develop() {
        console.log(`develop`);
    }
}

class Designer {
    design() {
        console.log(`design`);
    }
}

const work = (worker: Developer | Designer) => {
  
    // Type Guard
    if(worker instanceof Designer) {
        worker.design();
    } else if(worker instanceof Developer) {
        worker.develop();
    }
}

in

객체가 해당 프로퍼티를 가지고 있는지 파악할 때 사용하는 연산자

type Human = {
  speak: () => void;
};

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

function talk(thing: Human | Dog) {
  
    // Type Guard
    if ('speak' in thing) {
        return thing.speak();
    }
    return thing.bark();
}

thing 변수의 프로퍼티에 speak의 유무로 타입가드를 할 수 있다.

사용자 정의 타입가드

타입을 판단하는 방법을 직접 정의하고 싶을 때 User-defined type guards를 사용한다.

type Human = {
  speak: () => void;
};

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

function isHuman(thing: Human | Dog): thing is Human {
  return (thing as Human).speak !== undefined;
}

function talk(thing: Human | Dog) {
    if (isHuman(thing)) {
        return thing.speak();
    }
    return thing.bark();
}

sindresorhus/is 등의 유용한 오픈소스도 존재

Nullish Coalescing Operator

A ?? B
좌항이 null, undeifned인 경우에만 B를 리턴

기존의 A || B는 A가 falsy(false, 0, , '', ...)한 값인 경우 B를 반환하기 때문에 null, undefined를 제외한 falsy값을 그대로 반환하고 싶은 경우에 사용한다.

function getPrice(product: {price?: number}) {
  return product.price ?? -1;
}

console.log(getPrice({price: 0}));
// 0은 falsy한 값이지만 nullish Coalescing Operator를 사용하여 null, undefined를 제외한 falsy값을 그대로 반환
console.log(getPrice({})); // -1
function getPrice(product: {price?: number}) {
  return product.price || -1;
}

console.log(getPrice({price: 0}));
// || 연산자를 사용했다면 -1이 반환

Type Assertion (타입 단언)

TypeScript 의 타입추론은 매우 강력하지만 어쩔 수 없는 한계가 존재하기 때문에 프로그래머가 as 키워드를 사용하여 컴파일러에게 특정변수에 대한 타입 힌트를 주는 것을 타입 단언이라고 한다.

Type Assertion의 두 가지 형태

let someValue: unknown = 'this is a string';
// 1)
let strLength: number = (someValue as string).length;

// 2)
let strLength: number = (<string>someValue).length;

2) 꺽쇠(Angle bracket)형태는 JSX에서 문법적으로 혼동되기 때문에 1)방식을 주로 사용한다고 한다.

주의할 점 (타입 단언과 타입 캐스팅)

type Duck = {
  꽥꽥: () => void;
  헤엄: () => void;
}

// 타입 단언
const asDuck = {} as Duck;

// 타입 캐스팅
const duck: Duck = {
  꽥꽥() {
    console.log('꽥꽥');
  },
  헤엄() {
    console.log('어푸어푸');
  }
}

asDuck.꽥꽥(); // 컴파일 에러가 발생하지 않음

타입 단언은 강제하지 않기 때문에 컴파일 타임에는 에러가 발생하지 않는다.

타입 캐스팅은 객체의 프로퍼티를 강제한다.

하지만 실제로 실행해보면 프로퍼티가 존재하지 않아 에러가 발생하며 TypeScript를 사용하여 얻는 장점이 없어지기 때문에 지양하는것이 좋지 않을까 생각된다.

  • 데이터의 타입을 ‘변경’하는 것은 Type Casting
  • 데이터의 타입을 ‘명시’(알려주는 것)은 Type Assertion

String Literal & Numeric Literal

String Literal

특정 문자열만을 가진 타입을 지정해주고 싶을 때 사용하며 마우스 이벤트와 같이 이미 지정되어 있는 문자열들을 표현할 때 유용하다.

type EventNames = 'click' | 'doubleClick' | 'mouseDown' | 'mouseUp';

function handleEvent(eventName: EventNames) {}
handleEvent("click");
handleEvent("mouseDown");
handleEvent("ripleClick"); // 컴파일 에러

Numeric Literal

String Literal과 거의 동일 (문자열이 아닌 특정 숫자로민 이루어진 타입을 지정해주고 싶을 때)

function rollDie(): 1 | 2 | 3 | 4 | 5 | 6 {
    // ...
}

Reference

post-custom-banner

0개의 댓글