이 코드를 실행하면
const states = [
{name: 'Alabama', capital: 'Montgomery'},
{name: 'Alaska', capital: 'Juneau'},
{name: 'Arizona', capital: 'Phoenix'},
// ...
];
for (const state of states) {
console.log(state.capitol);
}
다음과 같은 결과가 나타난다.
undefined
undefined
undefined
이 코드는 자바스크립트에서는 오류 없이 실행이 된다. 하지만 반복문 내의 state.capitol는 일반적으로 state.capital일 것이다. 타입스크립트의 경우,
다음과 같이 오류를 잡아낸다.
const states = [
{name: 'Alabama', capital: 'Montgomery'},
{name: 'Alaska', capital: 'Juneau'},
{name: 'Arizona', capital: 'Phoenix'},
// ...
];
// END
for (const state of states) {
console.log(state.capitol);
// ~~~~~~~ Property 'capitol' does not exist on type
// '{ name: string; capital: string; }'.
// Did you mean 'capital'?
}
다음 예시를 살펴보자.
const x = 2 + '3'; // OK, type is string
const y = '2' + 3; // OK, type is string
위 코드는 다른 언어였다면 오류가 될만한 코드이다. 하지만 결과는 모두 문자열 '23'이 되는 자바스크립트 런티임 동작으로 모델링이 된다.
반대로 정상 동작하는 코드에 오류가 생기는 경우도 있다!
const a = null + 7; // Evaluates to 7 in JS
// ~~~~ Operator '+' cannot be applied to types ...
const b = [] + 12; // Evaluates to '12' in JS
// ~~~~~~~ Operator '+' cannot be applied to types ...
alert('Hello', 'TypeScript'); // alerts "Hello"
// ~~~~~~~~~~~~ Expected 0-1 arguments, but got 2
위 코드는 런타임 오류가 발생하지 않지만, 타입 체커는 문제점을 표시한다.
noImplicitAny 설정에 따라 오류가 나고 나지 않을 수 있다. 이 설정은 타입스크립트가 문제를 발견하기 수월해지고, 가독성이 좋아지며, 개발자의 생산성이 향상된다.
// tsConfig: {"noImplicitAny":false}
function add(a, b) {
return a + b;
}
// tsConfig: {"noImplicitAny":true}
function add(a, b) {
// ~ Parameter 'a' implicitly has an 'any' type
// ~ Parameter 'b' implicitly has an 'any' type
return a + b;
}
위 두 코드는 같은 코드이지만 설정에 따라서 오류의 여부가 달라진 다는 것을 알수 있다. 그래서 밑의 코드가 가장 명확하고 좋다.
// tsConfig: {"noImplicitAny":true}
function add(a: number, b: number) {
return a + b;
}
strictNullChecks 설정은 null과 undefined가 모든 타입에서 허용되는지 확인한다. 아래의 코드를 살펴보자. 이는 strictNullChecks을 설정하지 않을 경우, 유효한 코드임을 확인할 수 있다.
// tsConfig: {"noImplicitAny":true,"strictNullChecks":false}
const x: number = null; // OK, null is a valid number
하지만 반대로, 아래의 코드는 strictNullChecks 설정을 하여 null을 허용하지 않아 오류가 나는 코드이다. undefined의 경우에도 null과 같은 오류가 발생한다.
// tsConfig: {"noImplicitAny":true,"strictNullChecks":true}
const x: number = null;
// ~ Type 'null' is not assignable to type 'number'
이는 다음과 같이, null을 허용한다는 명시적인 코드를 통해서 오류를 해결할 수 있다.
// tsConfig: {"noImplicitAny":true,"strictNullChecks":true}
const x: number | null = null;
interface Square {
width: number;
}
interface Rectangle extends Square {
height: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape instanceof Rectangle) {
// ~~~~~~~~~ 'Rectangle' only refers to a type,
// but is being used as a value here
return shape.width * shape.height;
// ~~~~~~ Property 'height' does not exist
// on type 'Shape'
} else {
return shape.width * shape.width;
}
}
위 예시에서 instanceof에 대한 체크는 런타임에 일어나지만, Rectangle은 값이 아니라 타입이기 때문에 런티임 시점에 아무런 역할을 할 수가 없다. 자바스크립트가 컴파일되는 과정에서 모든 인터페이스, 타입, 타입 구문은 모두 제거되어 버리는 일이 발생한다.
위 코드에 대해 shape 타입을 명확하게 하기 위해서는 런타임에 타입 정보를 유지해야한다. 먼저 아래와 같이 shape 내에 height 속성의 존재 유무를 체크하는 것으로 가능하다.
function calculateArea(shape: Shape) {
if ('height' in shape) {
shape; // Type is Rectangle
return shape.width * shape.height;
} else {
shape; // Type is Square
return shape.width * shape.width;
}
다음으로 런타임에 접근 가능한 타입 정보를 명시적으로 저장하는 '태그'기법이 있다. 이 방법은 런타임에 타입 정보를 손쉽게 유지할 수 있기 때문에, 타입스크립트에서 흔히 볼 수 있다!!!
interface Square {
kind: 'square';
width: number;
}
interface Rectangle {
kind: 'rectangle';
height: number;
width: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape.kind === 'rectangle') {
shape; // Type is Rectangle
return shape.width * shape.height;
} else {
shape; // Type is Square
return shape.width * shape.width;
}
}
마지막으로 타입으로만 사용 가능한 interface 대신에 타입과 값 모두로 사용할 수 있는 class를 이용하는 방법이다.
class Square {
constructor(public width: number) {}
}
class Rectangle extends Square {
constructor(public width: number, public height: number) {
super(width);
}
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape instanceof Rectangle) {
shape; // Type is Rectangle
return shape.width * shape.height;
} else {
shape; // Type is Square
return shape.width * shape.width; // OK
}
}
이 파트 잘 이해 되지 않아서 일단 넘어가기
any 타입에는 타입 안정성이 없다. 다음과 같은 예시를 살펴보자.
let age: number;
age = '12';
// ~~~ Type '"12"' is not assignable to type 'number'
age = '12' as any; // OK
age += 1; // OK; at runtime, age is now "121"
원래 number타입을 any타입으로 바꾸었기 때문에 age는 '12'를 할당받아, string타입으로 바뀌어 age에 1을 더할 때 '121'이 된다!
또한 any 타입은 함수 시그니처를 무시한다. 함수를 호출하는 쪽은 약속된 타입의 입력을 제공하고, 함수는 약속된 타입의 출력을 반환한다. 그러나 any 타입은 이 약속을 어겨 함수의 시그니처를 무시할 수 있다.
function calculateAge(birthDate: Date): number {
// COMPRESS
return 0;
// END
}
let birthDate: any = '1990-01-19';
calculateAge(birthDate); // OK
이 함수의 시그니처는 매개변수가 Date 타입이어야 한다는 것이다. 하지만 위 코드에서는 any 타입을 사용하여 이 시그니처를 무시한 것이다.
참고 : 이펙티브 타입스크립트