이 글은 타입스크립트 입문 - 기초부터 실전까지의 타입단언 ~ 수업 마무리 부분을 보고 정리한 글입니다.
// 타입 단언(type assertion)
var a;
a = 20;
a = 'a';
var b = a as string;
현재
a
는 any타입인데a
에 숫자 20, 문자열 a같은 것들을 할당을 하면 개발자는 변수a
의 타입이 나중에 무엇이 될 것인지 알고 있다.
이러한 상황과 같이 타입스크립트보다 개발자가 타입을 더 잘알고 있는 경우, 직접as
문법을 사용하여 타입을 단언해줌.
타입 단언의 실제 사례는 DOM API
를 들어갈때 가장많이 사용!
var div = document.querySelector('div');
div.innerText
// 타입 단언
var div = document.querySelector('div') as HTMLDivElement;
div.innerText
위와 같은 코드에서 ts는
div
라는 변수에HTMLDivElement
혹은null
값이 담길 수 있기 때문에, 이 타입이 무엇인지 한번 더 검증하는 과정을 필요로 한다.
이러한 경우as HTMLDivElement
와 같이 개발자가 타입 단언을 하게되면 div의 타입은HTMLDivElement
로 되고, 더이상 에러가 발생하지 않는 것을 확인할 수 있다.
이와 같이 개발자가 타입스크립트보다 이 타입에 대해 정확하게 알고 있는 경우, 타입 단언을 사용하여 타입을 명시해줄 수 있다.
interface Developer {
name: string;
skill: string;
}
interface Person {
name: string;
age: number;
}
function introduce(): Developer | Person {
return { name: 'Tony', age: 33, skill: 'Iron Making'}
}
var tony = introduce();
console.log(tony.skill);
위의 코드에서
introduce
함수의 리턴 값은Developer
와Person
의Union
값이다. 따라서console.log(tony.skill)
부분에서 에러가 발생하는데 유니온 타입은 이 두가지 인터페이스의 공통된 속성인name
만 가지고 있다고 판단하기 때문이다.
if ((tony as Developer).skill) {
var skill = (tony as Developer).skill;
console.log(skill);
} else if((tony as Person).age) {
var age = (tony as Person).age;
console.log(age);
}
다음과 같은 타입 단언을 이용한 코드로
skill
과age
를 출력할 수 있다. 그러나 코드에서 볼 수 있듯이, 코드의 중복과 가독성 측면에서 좋지 않다. --> 이를 위해 타입 가드를 사용한다
// 타입 가드 정의
function isDeveloper(target: Developer | Person): target is Developer {
return (target as Developer).skill !== undefined;
}
if (isDeveloper(tony)) {
tony.skill;
} else {
tony.age
}
isDeveloper
라는 타입 가드를 사용하여target
이Developer
일 때와,Person
일 때를 구분한 것을 확인할 수 있다. 이를 활용한다면 아까 전의 중복되는 코드들보다 더 깔끔하게 코드를 작성할 수 있고, if 조건문 안에서 타입스크립트가 타입 추론을 하여 메서드를 제공해주는 것을 확인할 수 있다.
타입 호환이란 타입 스크립트에서 특정 타입이 다른 타입에 잘 맞는지를 의미한다.
interface Ironman {
name: string;
}
class Avengers {
name: string;
}
let i: Ironman;
i = new Avengers(); // OK, because of structural typing
코드의 구조 관점에서 타입이 서로 호환되는 것을 판단하기 때문에 에러가 발생하지 않는다.
참고 사이트: 타입호환
// 인터페이스
interface Developer {
name: string;
skill: string;
}
// interface Person {
// name: string;
// }
class Person {
name: string;
}
var developer: Developer;
var person: Person;
developer = person;
// person = developer // 에러 안남
developer
라는 변수는Developer
를 인터페이스로 하기 때문에name
과skill
속성이 필요한데,person
은name
속성만 가지고 있어 에러가 발생하게 된다. 이는inteface
와interface
뿐만아니라interface
와class
간 끼리도 타입호환을 하며, 구조적 관점에서 타입 호환을 검사한다.
// 함수
var add = function(a: number) {
}
var sum = function(a: number, b: number) {
}
sum = add;
// add = sum;
인터페이스와 클래스와는 다르게 함수에서는 인수로 전달받는 타입의 개수가 많은 것이 좀 더 넓은범위(구조적으로 더 큼)이기 때문에
sum=add
와 같은 할당은 되지만,add=sum
과 같은 할당을 하게되면 에러가 발생한다.
// 제네릭
interface Empty<T> {
}
var empty1: Empty<string>;
var empty2: Empty<number>;
empty1 = empty2;
empty2 = empty1;
interface NotEmpty<T> {
data: T;
}
var notempty1: NotEmpty<string>;
var notempty2: NotEmpty<number>;
notempty1 = notempty2; // 에러
notempty2 = notempty1; // 에러
제네릭에서 위의
Empty
의 경우, 인터페이스 안 구조가 비어이씩 때문에empty1
,empty2
둘다 할당을 하여도 문제가 되지 않는다. 그러나NotEmpty
는 인수를 받아data
에 타입을 주기 때문에notempty1
notempty2
가 서로 할당을 하게 되면 구조적 타이핑에 의해 타입이 다르므로 에러가 발생한다.
ts-modules/app.ts
interface Todo {
title: string;
checked: boolean;
}
var item: Todo = {
title: '할 일 1',
checked: false,
}
처음에 개발을 할 때에 코드가 짧을 때에는 같은 공간에 인터페이스와 변수를 같이 정의하여 사용하다가, 후에 코드의 길이가 길어지게되면 유지보수성과 가독성을 위해 코드를 분리하게 된다.
ts-modules/types.ts
export interface Todo {
title: string;
checked: boolean;
}
ts-modules/app.ts
import { Todo } from './types'
var item: Todo = {
title: '할 일 1',
checked: false,
}
ES6+ Modules개념과 유사하게 다음과 같이 코드를 모듈화 할 수 있다.
이 부분에 대한 내용은 자바스크립트 모듈화와 관련된 내용이라 아래 자료를 참고하면 좋을 것 같다.
참고자료: 자바스크립트-모듈
깃헙 실습 코드
=> 타입스크립트 모듈화
수업 정리 및 실전 프로젝트로 배우는 타입스크립트로 이어서 듣자