타입 어노테이션은 변수 혹은 상수에게 타입을 명시적으로 선언하여 어떤 타입의 값이 저장될 것인지를 컴파일러에게 알려주는 문법이다.
타입스크립트의 경우는 변수 이름 뒤에 : type
구문을 붙여 데이터 타입을 명시한다.
let age: number = 29;
let name: string = '상현';
let skills: string[] = ['javascript', 'typescript'];
let isAlive: boolean = true;
타입스크립트는 자바스크립트 코드에 타입을 점진적으로 적용시킬 수 있기 때문에 굳이 타입을 명시하지 않더라도 에러는 나지 않을 것이다. 그러나 타입을 명시하지 않으면 타입스크립트를 사용하는 이유가 사라지게 된다..
타입스크립트에서는 타입을 구조로 분리한다. 이것을 구조적 타이핑이라고 한다. 다음 예제를 보자.
interface Human {
age: number;
}
interface Car {
age: number;
}
let me: Human = { age: 29 };
let myCar: Car = { age: 50 };
me = myCar; // interface가 다르지만 interface의 구조가 같기 때문에 가능하다.
구조적 타이핑의 반대는 명목적 타이핑이다. 자바, C++ 등에서 사용한다.
타입스크립트의 타입은 값의 집합 으로 생각할 수 있다. 이 말은, 타입스크립트는 특정 값이 여러 개의 타입을 가질 수 있다는 것이다.
type stringOrNumber = string | number;
구조적 서브타이핑이란 객체가 가지고 있는 속성을 바탕으로 타입을 구분하는 것이다. 즉, 이름이 다른 객체라도 속성이 동일하면 호환이 가능하다.
interface Human {
age: number;
}
interface Car {
age: number;
engine: string;
}
let me: Human = { age: 29 };
let myCar: Car = { age: 50, engine: 'gasoilne' };
me = myCar; // interface가 다르지만 Car가 Human이 가지고 있는 속성을 포함하기에 가능하다.
타입스크립트가 자바스크립트를 모델링한 언어이기 때문이다. 자바스크립트는 덕 타이핑을 기본으로 한다.
덕 타이핑
동적 타이핑의 한 종류로 어떻게 만들어 졌든 타입에 걸맞은 변수와 메서드를 지니면 해당 타입에 속하는 것으로 간주한다.
"만약 어떤 새가 오리처럼 걷고, 헤엄치고, 꽥꽥거리는 소리를 낸다면 나는 그 새를 오리라고 부를 것이다."
타입스크립트는 자바스크립트의 특징을 그대로 받아서 이름으로 타입을 구분하는 것이 아닌 구조적 특징을 기반으로 타이핑 하는 방식을 채택했다. 이 방식 덕에 더 유연한 타이핑이 가능하다.
구조적 타이핑(TS)과 덕 타이핑(JS)은 비슷한 방식으로 겉으로 보기에는 차이가 없어 보인다.
둘의 차이는 타입을 검사하는 시점에 있다. 덕 타이핑(JS)은 런타임 시점에 타입을 검사하고, 구조적 타이핑(TS)은 컴파일 타임에 타입을 검사한다.
타입스크립트에서도 타입스크립트의 구조적 타이핑의 특징 때문에 예기치 못한 결과가 나올 때가 있다.
다음 예시를 보자.
interface Rectange {
width: number;
height: number;
}
function getTotalLineLength(r: Rectangle) {
let total = 0;
for (const axis of Object.keys(r)) {
total += r[axis];
}
}
getTotalLineLength({width: 10, height: 20}); // ok
getTotalLineLength({width: 30, height: 30, name:'정사각형'}); // error!
getTotalLineLength
함수의 인자로 Rectangle
을 받게 되어 있고, 두 번째로 호출한 라인에서 숫자 타입의 width
와 height
을 포함하고 있기에 통과가 되지만, name
이라는 속성이 추가로 들어가면서 원하는대로 동작하지 않게 되었다.
이러한 한계를 극복하고자 유니온 같은 방법이 생겨났다.
타입스크립트는 점진적으로 타입을 확인한다. 이 말은 타입 선언 생략을 허용한다는 것이다. 즉, 타입스크립트를 써도 타입을 전혀 지정하지 않고 자바스크립트처럼 써도 된다.
타입선언이 생략되면 암시적 타입 변환이 일어난다.
const add = (x, y) => return x+ y;
// 암시적 변환
const add: any = (x: any, y:any);
타입을 명시하지 않으면 모두 any
타입으로 추론한다.
타입스크립트에서 값과 타입은 서로 다른 네임스페이스에 존재한다. 그래서 다음과 타입과 값의 이름이 중복 되게 작성 할 수 있다.
type Person = { name: string; age: number; }
const Person = { job: 'developer' };
이렇게 작성이 가능한 이유는 타입스크립트가 컴파일되어 자바스크립트 파일로 변환될 때 타입스크립트 문법으로 작성된 내용은 모두 런타임에서 제거되기 때문이다.
타입스크립트는 코드의 문맥을 파악하여 스스로 이게 값인지, 타입인지를 판단한다. 값과 타입의 구분이 문맥에 따라 달라지기 때문에 혼동을 할 때도 있다. 다음 예시를 보자.
// email 함수에 options에 들어올 수 있는건 Person타입의 person, string 타입의 subject
const email = (options: {person: Person; subject: string}) => {
// ...
}
이를 구조 분해 할당으로 바꾸면… 오류 발생!!
// 오류 발생!!
const email = ({person: Person, subject: string}) => {
// ...
}
인수로 들어오는 Person
과 string
이 타입이 아닌 값으로(객체로) 해석됐기 때문에 오류가 발생한다. 이렇게 객체의 Key-Value
와 같이 혼동할 수 있는 경우 다음과 같이 타입을 구분해서 작성해야 한다.
const email = ({person, subject}: {person: Person, subject: string}) => {
// ...
}
위에서는 값과 타입이 다른 공간에 있기 때문에 이름을 중복해도 된다고 했지만, 값과 타입이 같은 공간에 있는 경우도 있다. 대표적으로 클래스와 enum
이다.
타입스크립트에서 클래스는 타입과 값으로 모두 사용될 수 있다. :
키워드 뒤에 클래스가 오면 타입으로, new
키워드 뒤에 오면 함수로 동작한다.
참조: 우아한 타입스크립트 with 리액트