타입 단언, 타입 가드, 타입 호환

y0ung·2020년 11월 28일
4

TypeScript

목록 보기
9/12
post-thumbnail

🔹 타입 단언

type assertion ?

let a; // let a:any

a = 10;
a = 'abc'

let b = a // let b:any;

b의 값을 할당 하기전 변수 a의 값이 변했지만 b의 타입은 여전히 any이다.

그래서 타입을 단언하는 방법은?

let b = a as string; 

as 를 사용해 최종적 으로 확실 하게 타입을 단언해준다.

실제로 활용해보기

타입 단언은 주로 DOM API를 조작할 때 많이 사용된다. 아래 예제를 보자.

let div = document.querySelector('div')// let div: HTMLDivElement | null

div.innerText;

타입을 지정해주기 전 div는 HTMLDivElement | null 일수 가 있어 오류가 생길수 있다.

let div = document.querySelector('div') as HTMLDivElement // let div: HTMLDivElement

div.innerText;


as로 정확하게 타입을 단언해 준다면 비로소 DOM API를 조작할수 있게 된다.

한마디로 타입단언을 하는 것은 '타입스크립트보다 개발자인 내가 더 이 타입에 대해서 자세히 알고 있으니 넌 나만 따라 와라' 라는 의미이다.

🔹 타입 가드

타입가드를 위한 예제

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

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

function introduce(): Developer | Person {
  return { name: 'Olive', age: 26, skill:'TypeScript'}
}

let Olive = introduce()
console.log(Olive.skill);

위의 코드를 보면 변수 Olive에 introduce()를 할당해 주어 Developer 이거나 Person의 인터페이스를 사용할수 있다. 하지만 Olive.skill을 살펴봤을때 skill형식이 없다고 나오게 된다. 이러한 오류를 해결해 보자

if((Olive as Developer).skill) {
  let skill = (Olive as Developer).skill;
  console.log(skill);
} else if ((Olive as Person).age){
  let age = (Olive as Person).age;
  console.log(age);
}

타입을 단언해서 Developer에 있는 skillPerson에 있는 age를 사용할수 있게 되지만 가독성이 떨어지게 된다. 이럴때 사용하는 게 타입 가드 이다.

타입가드를 적용해보기

function isDeveloper(target: Developer | Person): target is Developer {
  return ( target as Developer).skill !== undefined
}

if(isDeveloper(Olive)){
  Olive.skill
} else {
  Olive.age
}

is라는 키워드를 사용하는데 위의 코드를 해석하면 targetDeveloper이다.

🔹 타입 호환

타입 호환(Type Compatibility) 이란?

타입스크립트 코드에서 특정 타입이 다른 타입에 잘 맞는지를 의미함.

interface Ironman {
  name: string;
}

class Avengers {
  name: string;
}

let i: Ironman;
i = new Avengers(); // OK, because of structural typing

위와 같은 코드가 정상적으로 동작하는 이유는 자바스크립트의 작동방식과 관련이 있는데, 기본적으로 자바스크립트는 객체 리터럴이나 익명 함수 등을 사용하기 때문에 명시적으로 타입을 지정하는 것보다는 코드의 구조 관점에서 타입을 지정하는 것이 더 잘 어울린다.

구조적 타이핑 예시

structural typing : 코드 구조 관점에서 타입이 서로 호환되는지의 여부를 판단 하는 것.

interface Person {
  name: string;
}

let profile: Person;
// 타입스크립트는 olive의 타입을 {name:'string', skill:'string'} 으로 추론하였다.
let olive = {name:'Olive', skill:'TypeScript'};
profile = olive;

oliveprofile에 호환될 수 있는 이유는 olive의 속성중에 name이 있기 때문이다. Persond인터페이스에서 name속성을 갖고 있기 때문에 olivePerson타입에 호환될수 있다.

1. 함수

function introduce(a: Person){
  console.log("자기소개를 해보세요", a.name);
}
// 위에서 정의한 olive 변수, 타입은 {name:'string', skill:'string'}
introduce(olive);

olive 변수에 이미 name 속성 뿐만 아니라 skill속성도 있기 때문에 introduce 함수의 호출 인자로 넘길수 있다.

Soundness란?

타입 스크립트는 컴파일 시점에서 타입을 추론할 수 없는 특정 타입에 대해서 일단 안전하다고 보는 특성이 있다. 이걸 'it's said to not be sound" 라고 표현한다.

2. Enum 호환

Enumnumber 타입과 호환되지만 Enum 타입끼리는 호한 되지 않는다.

enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };

let status = Status.Ready;
status = Color.Green // Error

3. Class 호환

클래스 타입은 클래스 타입끼리 비교할 때 static member 와 constructor를 제외하고 속성만 비교한다.

class Peter {
  stature: number;
  constructor(name: string, value: number) { }
}

class Olive {
  stature: number;
  constructor(value: number) { }
}

let a: Peter;
let s: Olive;

a = s;  // OK
s = a;  // OK

4. Generics 호환

제네릭은 제네릭 타입 간의 호환 여부를 판단할 때 타입 인자 <T> 가 속성에 할당 되었는지를 기준으로 한다.

interface Empty<T> {
  //...
}
let x: Empty<number>;
let y: Empty<string>;

x = y;  // OK, because y matches structure of x
y = x

위 인터페이스는 일단 속성이 없기 때문에 xy는 같은 타입으로 간주된다. 하지만 만약 아래와 같이 인터페이스에 속성이 있어서 제네릭의 타입 인자가 속성에 할당된다면 얘기는 달라지게 된다.

interface NotEmpty<T> {
  data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;

x = y;  // Error, because x and y are not compatible

인터페이스 NotEmpty에 넘긴 제네릭 타입<T>이 data 속성에 할당되었으므로 xy는 서로 다른 타입으로 간주된다.


참고

타입스크립트 핸드북 - 타입 호환
캡틴판교_타입스크립트 입문

profile
어제보다는 오늘 더 나은

0개의 댓글