[우아한 타입스크립트 with 리액트] 2장. 타입
Soshy·

1. 타입이란
1.1 자료형으로서의 타입
컴퓨터의 메모리 공간은 한정적이다. 때문에 특정 메모리에 값을 효율적으로 저장하기 위해서는 먼저 해당 메모리 공간을 차지할 값의 크기를 알아야 한다. 값의 크기를 명시한다면 컴퓨터가 값을 참조할 때 한 번에 읽을 메모리 크기를 알 수 있어 값을 훼손하지 않고 가져올 수 있다.
최신 ECMAScript 표준을 따르는 자바스크립트는 다음과 같은 7가지 데이터 타입(자료형)을 정의한다.
undefinednullBooleanStringSymbolNumericObject
데이터 타입은 여러 종류의 데이터를 식별하는 분류 체계로 컴파일러에 값의 형태를 알려준다. 메모리의 관점에서 데이터 타입은 프로그래밍 언어에서 일반적으로 타입이라 부르는 개념과 같다. 개발자는 타입을 사용해서 값의 종류를 명시할 수 있고 메모리를 더욱 효율적으로 사용할 수 있다.
1.2 집합으로서의 타입
타입은 값이 가질 수 있는 유효한 범위의 집합을 말한다. 타입 시스템은 코드에서 사용되는 유효한 값의 범위를 제한해서, 런타임에서 발생할 수 있는 유효하지 않은 값에 대한 에러를 방지해준다.
함수 인자에 들어갈 값의 타입을 정의하지 않은 경우를 살펴보자.
function double(n) {
return n * 2;
}
double(2); // 4
double("2"); // NaN
double()의 내부 동작을 살펴보면 숫자를 인자로 받을 거라고 기대한다는 것을 알 수 있다. 만약 인자로 숫자가 아닌 다른 타입 값을 전달하면 의도치 않은 작업을 수행해서 원하는 값을 얻지 못한다.
하지만 함수의 매개변수 타입을 명시한다면 올바르지 않은 타입의 값으로 함수를 호출했을 때 타입스크립트 컴파일러는 곧바로 에러를 발생시킨다.
function double(n: number) {
return n * 2;
}
double(2); // 4
double("2"); // Error: Argument of type 'string' is not assignable to parameter of type 'number' (2345)
1.3 정적 타입과 동적 타입
타입을 결정하는 시점에 따라 타입을 정적 타입과 동적 타입으로 분류할 수 있다.
정적 타입 시스템에서는 모든 변수의 타입이 컴파일타임에 결정된다. 코드 수준에서 개발자가 타입을 명시해줘야 하는 C, 자바, 타입스크립트 등이 정적 타입 언어에 속한다. 컴파일타임에 타입 에러를 발견할 수 있기 때문에 프로그램의 안정성을 보장할 수 있다.
동적 타입 시스템에서는 변수 타입이 런타임에서 결정된다. 파이썬, 자바스크립트가 대표적인 동적 타입 언어이다. 프로그램을 실행할 때 타입 에러가 발견되기 때문에 언제 프로그램에 오류가 생길지 모르는 불안감에 휩싸이게 된다.
1.4 강타입과 약타입
개발자가 의도적으로 타입의 명시하거나 바꾸지 않았는데도 컴파일러 또는 엔진 등에 의해서 런타임에 타입이 자동으로 변경되는 것을 암묵적 타입 변환이라고 한다.
암묵적 타입 변환 여부에 따라 타입 시스템을 강타입과 약타입으로 분류할 수 있다.
- 강타입 특징을 가진 언어: 서로 다른 타입을 갖는 값끼리 연산을 시도하면 컴파일러 또는 인터프리터에서 에러가 발생한다. (파이썬, 루비, 타입스크립트)
- 약타입 특징을 갖는 언어: 서로 다른 타입을 갖는 값끼리 연산할 때는 컴파일러 또는 인터프리터가 내부적으로 판단해서 특정 값의 타입을 변환하여 연산을 수행한 후 값을 도축한다. (C++, 자바, 자바스크립트)
C++, 자바, 자바스크립트에서는 서로 다른 타입을 갖는 값(문자열/숫자)으로 빼기(-) 연산을 수행하면 정상적으로 동작한다. 자바스크립트에서는 문자열로 표기된 숫자를 실제 숫자 값으로 변환해서 빼기 연산을 한다. 이에 반해 파이썬, 루비, 타입스크립트에서는 컴파일러 혹은 인터프리터에서 타입 에러가 발생한다.
암묵적 변환은 개발자가 명시적으로 타입을 변환하지 않아도 다른 데이터 타입끼리 연산을 진행할 수 있는 편리함을 제공하지만, 작성자의 의도와 다르게 동작할 수 있기 때문에 예기치 못한 오류가 발생할 가능성도 높아진다.
const a = 3 + []; // "3"
const b = null + 12; // 12
let obj = {};
obj.foo; // undefined
function foo(num) {
return num / 2;
}
foo("bar"); // NaN
자바스크립트는 약타입 언어이기 때문에, 런타임에서 발생할 수 있는 에러를 예측하고 방지하는 코드를 작성하는 것이 프로그램을 안전하게 만드는 데 도움이 된다. 여기서 '안전한'이라는 표현은 타입 안정성을 의미한다. 타입을 명시해서 코드를 작성한 후에는, 프로그램 내에 기술된 개발자의 의도가 논리적으로 합당한지 검사하는 기준이 필요하다.
타입 검사기가 프로그램에 타입을 할당하는 데 사용하는 규칙 집합을 타입 시스템이라고 한다. 타입 시스템은 크게 두 가지로 구분한다.
- 어떤 타입을 사용하는지를 컴파일러에 명시적으로 알려줘야 하는 타입 시스템
- 자동으로 타입을 추론하는 타입 시스템
타입스크립트는 두 가지 타입 시스템의 영향을 모두 받았다. 즉, 개발자는 직접 타입을 명시하거나, 타입스크립트가 타입을 추론하도록 하는 방식 중에서 선택할 수 있다.
1.5 컴파일 방식
컴파일의 일반적인 의미는 사람이 이해할 수 있는 방식으로 작성한 코드를 컴퓨터가 이해할 수 있는 기계어로 바꿔주는 과정을 말한다. 개발자가 자바, C# 등의 고수준 언어로 소스코드를 작성하면, 컴파일러는 컴퓨터가 해석할 수 있는 바이너리 코드로 변환한다. 언어마다 컴파일 과정과 단계에 조금씩 차이가 있지만, 기본적으로 컴파일은 서로 다른 수준 간의 코드 변환을 의미한다.
하지만, 타입스크립트의 컴파일 결과물은 여전히 사람이 이해할 수 있는 방식인 자바스크립트 파일이다. 타입스크립트가 탄생한 이유는, 사람이 이해하기 쉬운 방식으로 코드를 작성하기 위해서가 아니라 자바스크립트의 컴파일타임에 런타임 에러를 사전에 잡아내기 위한 것이다. 타입스크립트를 컴파일하면 타입이 모두 제거된 자바스크립트 소스코드만이 남게 된다.
2. 타입스크립트의 타입 시스템
2.1 타입 애너테이션 방식
타입 애너테이션(annotation)이란 변수나 상수 혹은 함수의 인자와 반환 값에 타입을 명시적으로 선언해서 어떤 타입 값이 저장될 것인지를 컴파일러에 직접 알려주는 문법이다.
언어마다 타입을 명시해주는 방법은 다르다. 타입스크립트는 변수 이름 뒤에 : type 구문을 붙여 데이터 타입을 명시해준다.
let isDone: boolean = false;
let decimal: number = 6;
let color: string = "blue";
let list: number[] = [1, 2, 3];
let x: [string, number]; // tuple
2.2 구조적 타이핑
타입을 사용하는 여러 프로그래밍 언어에서 값이나 객체는 하나의 구체적인 타입을 가지고 있다. 타입은 이름으로 구분되며 컴파일타임 이후에도 남아있다. 이것을 명목적으로 구체화한 타입 시스템(Nominal Reified Type Systems)이라고 부르기도 한다.
일반적으로는 서로 다른 클래스끼리 명확한 상속 관계나 공통으로 가지고 있는 인터페이스가 없다면, 타입은 서로 호환되지 않는다.
그러나 타입스크립트에서 타입을 구분하는 방식은 조금 다르다. 이름으로 타입을 구분하는 명목적인 타입 언어의 특징과 다르게, 타입스크립트는 구조로 타입을 구분한다. 이것을 구조적 타이핑이라고 한다.
interface Developer {
faceValue: number;
}
interface BankNote {
faceValue: number;
}
let developer: Developer = { faceValue: 52 };
let bankNote: BankNote = { faceValue: 10000 };
developer = bankNote; // OK
bankNote = developer; // OK
2.3 구조적 서브타이핑
타입스크립트의 타입은 값의 집합으로 생각할 수 있다. 타입은 단지 집합에 포함되는 값이고, 특정 값은 많은 집합에 포함될 수 있다. 따라서 타입스크립트에서는 특정 값이 string 또는 number 타입을 동시에 가질 수 있다.
type stringOrNumber = string | number;
이처럼 집합으로 나타낼 수 있는 타입스크립의 타입 시스템을 지탱하고 있는 개념이 바로 구조적 서브타이핑이다.
구조적 서브타이핑이란 객체가 가지고 있는 속성(프로퍼티)을 바탕으로 타입을 구분하는 것이다. 이름이 다른 객체라도 속성이 동일하다면 타입스크립트는 서로 호환이 가능한 동일한 타입으로 여긴다.
아래 예제를 보자.
interface Pet {
name: string;
}
interface Cat {
name: string;
age: number;
}
let pet: Pet;
let cat: Cat = { name: "Zag", age: 2 };
pet = cat; // OK
Cat은 Pet과 다른 타입으로 선언되었지만, Pet이 가지고 있는 name이라는 속성을 가지고 있기 때문에, Cat 타입으로 선언한 cat을 Pet 타이브로 선언한 pet에 할당할 수 있다.
구조적 서브타이핑은 함수의 매개변수에도 적용된다.
interface Pet {
name: string;
}
let cat = { name: "Zag", age: 2 };
function greet(pet: Pet) {
console.log("Hello, " + pet.name);
}
greet(cat); // OK
greet() 함수의 매개변수에 들어갈 수 있는 값은 Pet 타입으로 제한되어 있지만, 타입을 명시하지 않은 cat 객체를 greet() 함수의 인자로 전달하여도 코드는 정상적으로 실행된다. cat 객체는 Pet 인터페이스가 가지고 있는 name 속성을 가지고 있어, pet.name의 방식으로 name 속성에 접근할 수 있기 때문이다.
타입스크립트의 서브타이핑 역시 구조적 타이핑을 기반으로 하고 있다. 아래 예시를 살펴보자.
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
class Developer {
name: string;
age: number;
sleepTime: number;
constructor(name: string, age: number, sleepTime: number) {
this.name = name;
this.age = age;
this.sleepTime = sleepTime;
}
}
function greet(p: Person) {
console.log(`Hello, I'm ${p.name}`);
}
const developer = new Developer("zig", 20, 7);
greet(developer); // Hello, I'm zig
Developer 클래스가 Person 클래스를 상속받지 않았는데도 greet(developer)는 정상적으로 동작한다. Developer는 Person이 갖고 있는 속성을 가지고 있기 때문이다.
서로 다른 두 타입 간의 호환성은 오로지 타입 내부의 구조에 의해 결정된다. 타입 A가 타입 B의 서브타입이라면 A 타입의 인스턴스는 B 타입이 필요한 곳에 언제든지 위치할 수 있다. 즉, 타입이 계층 구조로부터 자유롭다.
2.4 자바스크립트를 닮은 타입스크립트
타입스크립트의 타입 시스템은 구조적 서브타이핑을 사용하며, 이것은 명목적 타이핑과는 대조적인 방식이다. 명목적 타이핑은 타입의 구조가 아닌 타입의 이름만을 가지고 구별하는 것으로 C++, 자바 등에서 사용한다.
명목적 타이핑에서 두 변수는 같은 이름의 데이터 타입으로 선언된 경우에만 서로 호환된다. 구조가 같더라도 이름이 다르다면 다른 타입으로 취급한다.
명목적 타이핑은 타입의 동일성(equivalence)을 확인하는 과정에서 구조적 타이핑에 비해 조금 더 안전하다. 개발자가 의도한 타입이 아니라면 변수에 타입을 명시하는 과정에서 에러를 내뱉기 때문이다. 즉, 객체의 속성을 다른 객체의 속성과 호환되지 않도록 하여 안전성을 추구한다.
그런데도 타입스크립트가 구조적 타이핑을 채택한 이유는, 타입스크립트가 자바스크립트를 모델링한 언어이기 때문이다. 자바스크립트는 본질적으로 덕 타이핑(duck typing) 을 기반으로 한다. 덕 타이핑은 어떤 함수의 매개변수 값이 올바르게 주어진다면 그 값이 어떻게 만들어졌는지 신경 쓰지 않고 사용한다는 개념이다.
타입스크립트는 자바스크립트의 특징을 그대로 받아들여 명시적인 이름을 가지고 타입을 구분하는 대신, 객체나 함수가 가진 구조적 특징을 기반으로 타이핑하는 방식을 택했다. 구조적 타이핑 덕분에 타입스크립트는 더욱 유연한 타이핑이 가능해졌다.
자바스크립트의 덕 타이핑과 타입스크립트의 구조적 타이핑은 서로 구분되는 타이핑 방식이지만, 실제 사용하는 코드를 보면 차이가 없어 보일 수 있다.
덕 타이핑과 구조적 타이핑의 차이는 타입을 검사하는 시점에 있다. 덕 타이핑은 런타임에 타입을 검사하지만, 구조적 타이핑은 컴파일타임에 타입체커가 타입을 검사한다. 덕 타이핑은 주로 동적 타이핑에서, 구조적 타이핑은 정적 타이핑에서 사용된다.
2.5 구조적 타이핑의 결과
타입스크립트 구조적 타이핑의 특징 때문에 예기치 못한 결과가 나올 때도 있다.
interface Cube {
width: number;
height: number;
depth: number;
}
function addLines(c: Cube) {
let total = 0;
for (const axis of Object.keys(c)) {
// Element implicitly has an 'any' type
// because expression of type 'string' can't be used to index type 'Cube'
// No index signature with a parameter of type 'string'
// was found on type 'Cube'
const length = c[axis];
total += length;
}
}
타입스크립트는 c[axis]가 어떤 속성을 지닐지 알 수 없으며, c[axis] 타입을 number라고 확정할 수 없기 때문에 에러를 발생시킨다.
이러한 한계를 극복하고자 타입스크립트에 명목적 타이핑 언어의 특징을 가미한 식별할 수 있는 유니온(Discriminated Unions) 같은 방법이 생겨났다.
2.6 타입스크립트의 점진적 타입 확인
타입스크립트는 점진적으로 타입을 확인하는 언어이다. 점진적 타입 검사란, 컴파일타임에 타입을 검사하면서 필요에 따라 타입 선언 생략을 허용하는 방식이다. 타입을 지정한 변수와 표현식은 정적으로 타입을 검사하지만, 타입 선언이 생략되면 동적으로 검사를 수행한다. 타입 선언을 생략하면 암시적 타입 변환이 일어난다.
function add(x, y) {
return x + y;
}
// 위 코드는 아래와 같이 암시적 타입 변환이 일어난다.
function add(x: any, y: any): any;
타입스크립트에서는 필요에 따라 타입을 생략할 수도 있고, 타입을 점진적으로 추가할 수도 있다. 하지만, 타입스크립트는 컴파일타임에 프로그램의 모든 타입을 알고 있을 때 최상의 결과를 보여준다.
타입 스크립트의 타입 시스템은 정적 타입의 정확성을 100% 보장해주지 않는다. 모든 변수와 표현식의 타입을 컴파일타임에 검사하지 않아도 되기 때문에, 타입이 올바르게 정해지지 않으면 런타임에서 에러가 발생하기도 한다.
const names = ["zig", "collin"];
console.log(names[2].toUpperCase());
// TypeError: Cannot read property 'toUpperCase' of undefined
noImplicitAny타입스크립트 컴파일 옵션 중 하나로, 타입 애너테이션이 없을 때 변수가
any타입으로 추론되는 것을 허락하지 않는다. 타입스크립트로 코드를 작성할 때에는 정확한 타이핑을 위해tsconfig의noImplicitAny옵션을true로 설정하는 것이 좋다.
2.7 자바스크립트 슈퍼셋으로서의 타입스크립트
타입스크립트는 기존 자바스크립트 코드에 정적인 타이핑을 추가한 것으로, 자바스크립트의 상위 집합이다. 타입스크립트 문법은 모든 자바스크립트 문법을 포함하고 있다.
모든 자바스크립트 코드는 타입스크립트라고 볼 수 있지만, 반대로 모든 타입스크립트 코드가 자바스크립트 코드인 것은 아니다.
2.8 값 vs 타입
값(value)은 프로그램이 처리하기 위해 메모리에 저장하는 모든 데이터이다. 객체 역시 값이다. 그리고 자바스크립트에서는 함수도 값이다. 모든 것이 객체인 언어답게 자바스크립트 함수는 런타임에 객체로 변환되기 때문이다.
값은 변수에 할당할 수 있다. 함수 역시 값으로 볼 수 있기 때문에, 함수 역시 변수에 할당할 수 있다.
자바스크립트 대신 타입스크림트를 사용하게 되면서 타입이라는 개념이 등장한다. 타입스크립트는 변수, 매개변수, 객체 속성 등에 : type 형태로 타입을 명시한다. 또는 type이나 interface 키워드로 커스텀 타입을 정의할 수도 있다.
값 공간과 타입 공간의 이름은 서로 충돌하지 않기 때문에, 타입과 변수를 같은 이름으로 정의할 수 있다. 이는 타입스크립트가 자바스크립트의 슈퍼셋인 것과 관련이 있다. 타입스크립트의 문법인 type으로 선언한 내용은 자바스크립트 런타임에서 제거되기 때문에, 값 공간과 타입 공간은 서로 충돌하지 않는다.
타입스크립트에는 값과 타입 공간에 동시에 존재하는 심볼도 있다. 대표적인 것이 클래스와 enum이다.
타입스크립트에서 헷갈리는 것 중 하나가 클래스에 관한 것이다. 자바스크립트 ES6에서 등장한 클래스는 객체 인스턴스를 더욱 쉽게 생성하기 위한 문법 기능(syntactic sugar)으로 실제 동작은 함수와 같다.
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
const rect1 = new Rectangle(5, 4);
동시에, 클래스는 타입으로도 사용된다.
즉, 타입스크립트 코드에서 클래스는 값과 타입 공간 모두에 포함될 수 있다.
class Developer {
name: string;
domain: string;
constructor(name: string, domain: string) {
this.name = name;
this.domain = domain;
}
}
const me: Devleoper = new Developer("zig", "frontend");
변수명 me 뒤에 등장하는 : Developer에서 Develper는 타입에 해당하지만, new 키워드 뒤의 Developer는 클래스의 생성자 함수인 값으로 동작한다.
타입스크립트에서 클래스는 타입 애너테이션으로 사용할 수 있지만, 런타임에서 객체로 변환되어 자바스크립트의 값으로도 사용되는 특징을 가지고 있다.
클래스와 마찬가지로, 타입스크립트 문법인 enum 역시 런타임에 객체로 변환되는 값이다. enum은 런타임에 실제 객체로 존재하며, 함수로 표현할 수도 있다.
타입스크립트에서 어떠한 심볼이 값으로 사용된다는 것은 컴파일러를 사용해서 타입스크립트 파일을 자바스크립트 파일로 변환해도 여전히 자바스크립트 파일에 해당 정보가 남아있음을 의미한다. 반면, 타입으로만 사용되는 요소는 컴파일 이후에 자바스크립트 파일에서 해당 정보가 사라진다.
2.9 타입을 확인하는 방법
타입스크립트에서 typeof, instanceof, 타입 단언을 사용해서 타입을 확인할 수 있다.
2.9.1 typeof
typeof는 연산하기 전에 피연산자의 데이터 타입을 나타내는 문자열을 반환한다. typeof는 자바스크립트의 7가지 기본 데이터 타입과 Function, 호스트 객체, Object 객체를 반환할 수 있다.
typeof 2022; // "number"
typeof true; // "boolean"
typeof {}; // "object"
타입스크립트에는 값 공간과 타입 공간이 별도로 존재하기 때문에, typeof 연산자도 값에서 쓰일 때와 타입에서 쓰일 때의 역할이 다르다.
interface Person {
first: string;
last: string;
}
const person: Person = { first: "zig", last: "song" };
function email(options: { person: Person; subject: string; body: string }) {}
-
값에서 사용된
typeof는 자바스크립트 런타임의typeof연산자가 된다.const v1 = typeof person; // 'object' const v2 = typeof email; // 'function' -
타입에서 사용된
typeof는 값을 읽고 타입스크립트 타입을 반환한다.type T1 = typeof person; // Person type T2 = typeof email; // (options: { person: Person; subject: string; body: string; }) => void
자바스크립트 클래스는 typeof 연산자를 쓸 때 주의해야 한다. 아래 예제로 확인해보자.
class Developer {
name: string;
sleepingTime: number;
constructor(name: string, sleepingTime: number) {
this.name = name;
this.sleepingTime = sleepingTime;
}
}
const d = typeof Developer; // 'function'
type T = typeof Developer; // typeof Developer
타입 공간에서의 typeof Developer 반환 값이 조금 특이한데, type T에 할당된 Developer는 인스턴스의 타입이 아니라 new 키워드를 사용할 때 볼 수 있는 생성자 함수이기 때문이다.
const zig: Developer = new Developer("zig", 7);
type ZigType = typeof zig; // Developer
Developer 클래스로 생성한 zig 인스턴스는 Developer가 인스턴스 타입으로 생성되었기 때문에 타입 공간에서의 typeof zig인 Developer를 반환한다.
2.9.2 instanceof
자바스크립트에서 instanceof 연산자를 사용하면, 프로토타입 체이닝 어딘가에 생성자의 프로토타입 속성이 존재하는지 판단할 수 있다. typeof 연산자처럼, instanceof 연잔자의 필터링으로 타입이 보장된 상태에서 안전하게 값의 타입을 정제하여 사용할 수 있다.
let error = unknown;
if (error instanceof Error) {
showAlertModal(error.message);
} else {
throw Error(error);
}
2.9.3 타입 단언
타입스크립트에서는 타입 단언을 통해 타입을 강제할 수도 있는데, as 키워드를 사용하면 된다. 타입 단언은 개발자가 해당 값의 타입을 더 잘 파악할 수 있을 때 사용되며, 강제 형 변환과 유사한 기능을 제공한다.
// 어딘가에서 unknown 타입 값을 전달받았다고 가정
const loaded_text: unknown;
const validateInputText = (text: string) => {
if (text.length < 10) return "최소 10글자 이상 입력해야 합니다.";
return "정상 입력된 값입니다.";
};
// as 키워드를 사용해서 string으로 강제하지 않으면 타입스크립트 컴파일러 단계에서 에러 발생
validateInputText(loaded_text as string);
3. 원시 타입
자바스크립트에서 값은 타입을 가지지만, 변수는 별도의 타입을 가지지 않는다. 타입스크립트는 이 변수에 타입을 지정할 수 있는 타입 시스템 체계를 구축한다. 자바스크립트의 7가지 원시 값은 타입스크립트에서 원시 타입으로 존재한다.
3.1 boolean
const isEmpty: boolean = true;
const isLoading: boolean = false;
// errorAction.type과 ERROR_TEXT가 같은지 비교한 결괏값을 boolean 타입으로 반환하는 함수
function isTextError(errorCode: ErrorCodeType): boolean {
const errorAction = getErrorAction(errorCode);
if (errorAction) {
return errorAction.type === ERROR_TEXT;
}
return false;
}
오직 true와 false 값만 할당할 수 있는 타입이다. 비교식의 결과도 boolean 타입을 갖는다.
자바스크립트에는 boolean 원시 값은 아니지만 형 변환을 통해 true / false로 취급되는 Truthy / Falsy 값이 존재한다. 이 값은 boolean 원시 값이 아니므로, 타입스크립트에서도 boolean 타입에 해당하지 않는다.
3.2 undefined
let value: string;
console.log(value); // undefined (값이 아직 할당되지 않음)
type Person = {
name: string;
job?: string;
};
정의되지 않았다는 의미의 타입으로 오직 undefined 값만 할당할 수 있다. 일반적으로 초기화되지 않은 값을 의미한다.
3.3 null
let value: null | undefined;
console.log(value); // undefined (값이 아직 할당되지 않음)
value = null;
console.log(value); // null
오직 null만 할당할 수 있다. 자바스크립트에서 보통 빈 값을 할당해야 할 때 사용한다.
3.4 number
const maxLength: number = 10;
const maxWidth: number = 120.3;
const maximum: number = +Infinity;
const notANumber: number = NaN;
자바스크립트의 숫자에 해당하는 모든 원시 값을 할당할 수 있다. 자바스크립트의 숫자는 정수, 부동소수점수를 구분하지 않기 때문에, 모두 number 타입에 할당할 수 있다. 자바스크립트에서 숫자에 해당하는 원시 값 중 NaN이나 Infinity도 포함된다.
3.5 bigInt
const bigNumber1: bigint = BigInt(999999999999);
const bigNumber2: bigInt = 999999999999n;
ES2020에서 새롭게 도입된 데이터 타입으로, 타입스크립트 3.2 버전부터 사용 가능하다. number 타입과 bigInt 타입은 상호 작용이 불가능하다.
3.6 string
const receiverName: string = 'KG';
const receiverPhoneNumber: string = "010-0000-0000";
const letterContent: string = `안녕, 내 이름은 ${senderName}이야.`;
문자열을 할당할 수 있는 타입이다. 공백도 string 타입에 해당한다.
3.7 symbol
const MOVIE_TITLE = Symbol("title");
const MUSIC_TITLE = Symbol("title");
console.log(MOVIE_TITLE === MUSIC_TITLE); // false
let SYMBOL: unique symbol = Symbol(); // A variable whose type is a 'unique symbol' type must be 'const'
ES2015에서 도입된 데이터 타입으로 Symbol() 함수를 사용하면 어떤 값과도 중복되지 않는 유일한 값을 생성할 수 있다. 타입스크립트에는 symbol 타입과 const 선언에서만 사용할 수 있는 unique symbol 타입이라는 symbol의 하위 타입도 있다.
타입스크립트의 모든 타입은 기본적으로 null과 undefined를 포함하고 있다. 하지만 ts-config의 strictNullChecks 옵션을 활성화했을 때는 사용자가 명시적으로 해당 타입에 null이나 undefined를 포함해야만 null과 undefined를 사용할 수 있다.
4. 객체 타입
앞서 언급한 7가지 원시 타입에 속하지 않는 값은 모두 객체 타입으로 분류할 수 있다.
4.1 object
자바스크립트 객체의 정의에 맞게 대응하는 타입스크립트 타입 시스템이다. object 타입은 가급적 사용하지 말도록 권장되는데, any 타입과 유사하게, 객체에 해당하는 모든 타입 값을 유동적으로 할당할 수 있어 정적 타이핑의 의미가 크게 퇴색되기 때문이다.
function isObject(value: object) {
return (
Object.prototype.toString.call(value).replace(/\[|\]|\s|object/g, "") === "Object"
);
}
// 객체, 배열, 정규 표현식, 함수, 클래스 등 모두 object 타입과 호환된다
isObject({});
isObject({ name: "KG" });
isObject([0, 1, 2]);
isObject(new RegExp("object"));
isObject(function () {
console.log("hello wolrd");
});
isObject(class Class {});
// 그러나 원시 타입은 호환되지 않는다
isObject(20); // false
isObject("KG"); // false
4.2 {}
중괄호({})는 자바스크립트에서 객체 리터럴 방식으로 객체를 생성할 때 사용한다. 타입스크립트에서 객체를 타이핑할 때도 중괄호를 쓸 수 있는데, 중괄호 안에 객체의 속성 타입을 지정해주는 식으로 사용한다. 이것은 타이핑되는 객체가 중괄호 안에서 선언된 구조와 일치해야 한다는 것을 의미한다.
// 정상
const noticePopup: { title: string; description: string } = {
title: "IE 지원 종료 안내",
description: "2022.07.15일부로 배민상회 IE 브라우저 지원을 종료합니다.",
};
// SyntaxError
const noticePopup: { title: string; description: string } = {
title: "IE 지원 종료 안내",
description: "2022.07.15일부로 배민상회 IE 브라우저 지원을 종료합니다.",
startAt: "2022.07.15 10:00:00", // startAt은 지정한 타입에 존재하지 않으므로 오류
};
자바스크립트에서 빈 객체를 생성하기 위해 const obj = {};와 같은 구문을 사용할 수 있다. 타입스크립트 역시 이에 대응하는 타입으로 {}를 사용할 수 있는데, 자바스크립트와 마찬가지로 빈 객체임을 의미한다. 따라서 {} 타입으로 지정된 객체에는 어떤 값도 속성으로 할당할 수 없다.
빈 객체를 지정하기 위해서는
{}보다 유틸리티 타입으로Record<string, never>처럼 사용하는 것이 바람직하다.
let noticePopup: {} = {};
noticePopup.title = "IE 지원 종료 안내"; // (X) title 속성을 지정할 수 없음
{} 타입으로 지정된 객체는 완전히 비어있는 순수한 객체를 의미하는 것이 아니다. 자바스크립트 프로토타입 체이닝으로 Object 객체 래퍼에서 제공하는 속성에는 정상적으로 접근할 수 있다.
4.3 array
자바스크립트의 배열 자료구조는 원소를 자유롭게 추가하고 제거할 수 있으며 타입 제한 없이 다양한 값을 다룬다. 그러나 이러한 쓰임은 타입스크립트가 추구하는 정적 타이핑 방향과는 맞지 않는다.
타입스크립트에서는 배열을 array라는 별도의 타입으로 다룬다. 타입스크립트 배열 타입은 하나의 타입 값만 가질 수 있다. 하지만 자바스크립트와 마찬가지로 원소 개수는 타입에 영향을 주지 않는다.
타입스크립트에서 배열 타입을 선언하는 방식은 Array 키워드로 선언하거나 대괄호([])를 사용해서 선언하는 방법이 있다. 두 방식은 결과론적으로 같으니, 취향에 따라 사용하면 된다.
const getCartList = async (cartId: number[]) => {
const res = await CartApi.GET_CART_LIST(cartId);
return res.getData();
};
getCartList([]); // (O) 빈 배열도 가능하다
getCartList([1001]); // (O)
getCartList([1001, 1002, 1003]); // (O) number 타입 원소 몇 개가 들어와도 상관없다
getCartList([1001, "1002"]); // (X) '1002'는 string 타입이므로 불가하다
주의해야 할 점은 튜플 타입도 대괄호로 선언한다는 것이다. 타입스크립트 튜플 타입은 배열과 유사하지만, 튜플의 대괄호 내부에는 선언 시점에 지정해준 타입 값만 할당할 수 있다. 원소 개수도 타입 선언 시점에 미리 정해진다.
const targetCodes: ["CATEGORY", "EXHIBITION"] = ["CATEGORY", "EXHIBITION"]; // (O)
const targetCodes: ["CATEGORY", "EXHIBITION"] = [
"CATEGORY",
"EXHIBITION",
"SALE",
]; // (X) SALE은 지정할 수 없음
4.4 type과 interface 키워드
흔히 객체를 타이핑하기 위해 자주 사용하는 키워드로 type과 interface가 있다.
type NoticePopupType = {
title: string;
description: string;
};
interface INoticePopup {
title: string;
description: string;
}
const noticePopup1: NoticePopupType = { ... };
const noticePopup2: INoticePopup = { ... };
4.5 function
자바스크립트에서는 함수도 일종의 객체로 간주하지만, typeof 연산자로 함수 타입을 출력해보면, 자바스크립트는 함수를 function이라는 별도 타입으로 분류한다는 것을 알 수 있다.
function add(a, b) {
return a + b;
}
console.log(typeof add); // 'function'
타입스크립트에서도 함수를 별도 함수 타입으로 지정할 수 있다. 다만, 객체의 타이핑과 달리 주의해야 할 점이 있다.
- 자바스크립트에서
typeof연산자로 확인한function이라는 키워드 자체를 타입으로 사용하지 않는다. - 함수는 매개변수 목록을 받을 수 있지만, 타입스크립트에서는 매개변수도 별도 타입으로 지정해야 한다.
function add(a: number, b: number): number {
return a + b;
}
함수 자체의 타입을 지정할 때는 호출 시그니처를 정의하는 방식을 사용하면 된다.
type add = (a: number, b: number) => number;
호출 시그니처(Call Signature)
타입스크립트에서 함수 타입을 정의할 때 사용하는 문법이다. 함수 타입은 해당 함수가 받는 매개변수와 반환하는 값의 타입으로 결정된다. 호출 시그니처는 이러한 함수의 매개변수와 반환 값의 타입을 명시하는 역할을 한다.