값과 이 값으로 할 수 있는 일의 집합
숫자를 매개변수로 받는 squareOf
라는 함수가 있다고 가정하자. 만약 매개변수가 숫자가 아닌 다른 타입을 전달한다면 유효하지 않은 작업을 수행하게 된다. 따라서 매개변수의 타입을 명시해야 한다.
function squareOf (n: number) {
return n * n;
}
console.log(squareOf(2)); // 4
console.log(squareOf('n')); // Error TS2345
위와 코드를 통해 숫자가 아닌 타입을 건네면 타입스크립트가 바로 에러를 발생시킨다.
any
는 타입의 대부라고 할 수 있다. any
로 뭐든지 할 수 있지만 꼭 필요한 상황이 아니라면 사용하지 않는 것이 좋다. any
는 타입스크립트와 프로그래머 둘 다 타입을 알 수 없는 상황에서는 기본 타입인 any
라고 가정한다. any
는 최후의 보루로, 가급적 사용하지 않아야 한다.
any
를 사용하면 값이 자바스크립트처럼 동작하기 시작하면서 타입 검사기라는 마법이 더이상 작동하지 않게 된다.
let a: any = 666;
let b: any = ['danger'];
let c = a + b // any
변수 c
는 에러가 발생할 것 같지만 타입스크립트에 두 개의 any
를 더하라고 지시했으므로 에러가 발생하지 않는다. any
를 사용하려면 명시적으로 선언해야 한다.
타입을 미리 할 수 없는 어떤 값이 있을 때에는 any
대신 unknown
을 사용하자. any
처럼 unknown
도 모든 값을 대표하지만, unknown
의 타입을 검사해 정제하기 전까지는 타입스크립트가 unknown
타입의 값을 사용할 수 없게 강제한다.
unknown
은 비교 연산과 반전을 지원하고, 자바스크립트의 typeof
, instanceof
연사자로 정제할 수 있다.
boolean
은 참과 거짓을 갖는다. 이 타입으로는 비교 연산과 반전 연산을 할 수 있다.
const
를 사용하여 boolean
타입의 값을 할당하면 해당 변수는 타입이 boolean
이 아닌 true
혹은 false
로 설정된다. 이는 boolean
타입이 가질 수 있는 값 중 특정한 하나의 값으로 한정된다. 이를 타입 리터럴이라고 부르는데, 오직 하나의 값을 나타내는 타입이다. const
사용 뿐 아니라, 명시적으로 true
혹은 false
로 설정할 수 있다.
number
타입은 모든 숫자의 집합이다. 타입은 사칙 연산 및 모듈로, 비교 등 숫자 관련 연산을 수행할 수 있다.
bigint
는 자바스크립트와 타입스크립트에 새로 추가된 타입으로, 이를 이용하면 라운딩 관련 에러 걱정 없이 큰 정수를 처리할 수 있다. number
는 2^53까지의 정수를 표현할 수 있지만 bigint
를 이용하면 이보다 큰 수도 표현할 수 있다. bigint
타입은 모든 BigInt
의 집합으로 사칙 연산, 비교 등의 연산을 지원한다. bigint
는 다음처럼 사용할 수 있다.
let a = 1234n;
const b = 1234n;
let c = a + b; // bigint
let e = 88.5n; // Error TS1353, bigint 리터럴은 반드시 정수여야 함
let f: bigint = 1234n;
let g: 100n = 100n;
let h: bigint = 1234; // Error TS2322L '100' 타입은 'bigint' 타입에 할당할 수 없음
symbol
은 ES2015에 새로 추가된 기능이다. 보통 객체와 맵에서 문자열 키를 대신하는 용도로 사용한다. 심벌 키를 사용하면 사람들이 잘 알려진 키만 사용하도록 강제할 수 있으므로 키를 잘못 설정하는 실수를 방지한다. 객체의 기본 반복자(Symbol.iterator
)를 설정하거나 객체가 어떤 인스턴스인지를 런타임에 오버라이딩하는 것과 비슷한 기능을 제공한다. symbol
타입으로는 할 수 있는 동작이 별로 없다.
symbol
은 symbol
타입으로 추론되거나 아니면 명시적으로 unique symbol
로 정의할 수 있다.
const e = Symbol('e')
const f: unique symbol = Symbol('f');
let g: unique symbol = Symbol('f'); // Error TS1332: 'unique symbol' 타입은 반드시 'const'여야 함
let h = e === e // boolean
iet i = e === f // Error TS2367: 'unique symbol' 타입은 서로 겹치는 일이 없으므로 이 비교문의 결과는 항상 'false'
타입스크립트의 객체 타입은 객체의 형태를 정의한다. 타입스크립트에서 객체를 서술하는 데 타입을 이용하는 방식은 여러 가지다. 첫 번째 방법은 값을 object
로 선언하는 것이다.
let a: object = {
b: 'x',
}
console.log(a.b); // Error TS2339: 'b' 프로퍼티는 'object'에 존재하지 않음
object
타입을 명시했을 때 값의 프로퍼티에 접근할 수 없다. object
는 서술하는 값에 관한 정보를 거의 알려주지 않으며, 값 자체가 자바스크리브 객체라고 말해줄 뿐이다. 명시적으로 정의하지 않고 자바스크립트가 추론하는 방법도 있다.
타입스크립트에서 object
는 const
로 선언 해도 값은 바꿀 수 있다. 그래서 프로퍼티의 값을 할당해도 타입스크립트는 값 자체를 타입으로 나타내지 않고(타입 리터럴), 해당 값의 타입을 추론한다.
let c: { fitstName: string; lastName: string } = {
firstName: 'john',
lastName: 'barrowman',
}; // OK
let a: { b: number } = {}; // Error TS2741: '{}' 타입에는 {b: number}타입에 필요한 'b' 프로퍼티가 없음
a = {
b: 1,
c: 2,
} // Error TS2322: {b: number; c: number} 타입을 {b: number} 타입에 할당 할 수 없음.
기본적으로 타입스크립트는 객체 프로퍼티에 엄격한 편이다. 예를 들어 객체에 number
타입의 b
라는 프로퍼티가 있어야 한다고 정의하면 타입스크립트는 오직 b
만 기대한다. b
가 없거나 다른 추가 프로퍼티가 있으면 에러를 발생시킨다.
만약 어떤 프로퍼티는 선택형이고 예정에 없던 프로퍼티가 추라될 수 있다고 타입스크립트에게 알려주려면 아래의 코드를 통해 설정하는 방법도 있다.
let a : {
b: number; // a는 number 타입의 프로퍼티 b를 포함한다.
c?: string; // a는 string 타입의 프로퍼티 c를 포함할 수 있다.
[key: number]: boolean // (인덱스 시그니쳐)a는 boolean 타입을 갖는 number 타입의 프로퍼티 여러 개를 포함할 수 있다.
}
객체 타입을 정의할 때 선택형만 사용할 수 있는 것은 아니다. 필요하면 readonly
한정자를 이용해 특정 필드를 읽기 전용으로 정의할 수 있다.
let user: {
readonly firstName: string
} = {
firstName: 'abby',
}
user.firstName = 'abbey with e' // Error TS2540: 'firstName'은 읽기 전용 프로퍼티이므로 할당할 수 없음
객체 리터럴 표기법에는 빈 객체 타입({})이라는 특별한 상황이 존재한다. null
과 undefined
를 제외한 모든 타입은 빈 객체 타입에 할당할 수 있으나, 이는 사용하기 까다롭게 만든다. 따라서 가능한 한 빈 객체는 피하는 게 좋다.
변수를 선언해서 값 대신 변수로 칭하듯이 타입 별칭으로 타입을 가리킬 수 있다.
type Age = number;
type Person = {
name: string;
age : Age; // number
}
타입 별칭을 이용하면 Person
의 형태를 조금 더 이해하기 쉽게 정의할 수 있다. 타입스크립트는 별칭을 추론하지는 않으므로 반드시 별칭의 타입을 명시적으로 정의해야 한다.
일반적으로 타입 별칭은 하나의 타입을 두 번 정의할 수 없다. 그러나 모든 블록과 함수는 자신만의 영역을 가지므로 내부에 정의한 타입 별칭이 외부의 정의를 덮어쓴다.
type Color = 'red';
let x = Math.random() < 0.5;
if (x) {
type Color = 'blue' // 위의 Color 정의를 덮어씀
let b: Color = 'blue';
} else {
let c: Color = 'red';
}
타입 별칭은 복잡한 타입을 DRY하지 않도록 해주며 변수가 어떤 목적으로 사용되었는지 쉽게 이해할 수 있게 도와준다.
A, B라는 두 사물이 있을 때 이를 유니온하면 둘을 합친 결과가 나오며 인터섹션하면 둘의 공통 부분이 결과로 나온다.
타입스크립트는 타입에 적용할 수 있는 특별한 연산자인 유니온(|
)과 인터섹션(&
)을 제공한다.
type Cat = { name: string; purrs: boolean };
type Dog = { name: string; barks: boolean; wags: boolean };
type CarOrDogOrBoth = Cat | Dog;
type CatAndDog = Cat & Dog;
유티온 타입에 사용된 값이 꼭 유니온을 구성하는 타입 중 하나일 필요는 없으며 양쪽 모두에 속할 수 있다.
자바스크립트처럼 타입스크립트 배열도 동일하게 특별한 객체다.
let a = [1, 2, 3]; // number[]
let b = ['a', 'b']; // string[]
let c: string[] = ['a']; // string[]
let d = [1, 'a']; // (string | number)[]
let e = [2, 'b']; // (string | number)[]
let f = ['red'];
f.push('blue');
f.push(true) // Error TS2345: 'true' 타입 인수를 'string' 타입 매개변수에 할당할 수 없음
let g = [] // any[]
g.push(1) // number[]
g.push('red') // Error TS2345: 'red' 타입 인수를 'number'타입 매개변수에 할당할 수 없음
위의 예제로 알 수 있는 사실은 초기 선언이 어떻게 되었는지에 따라 타입스크립트가 추론하고, 그 이후에 push
등 배열에 메서드를 사용했을 때 추론된 타입 이외의 타입이 발견되면 에러를 발생시킨다.
g
는 특별한 상황으로, 빈 배열에 초기화하면 타입스크립트는 배열의 요소 타입을 알 수 없으므로 any
일 것으로 추측한다. 배열을 조작하여 요소를 추가하면 타입스크립트가 주어진 정보를 이용해 배열의 타입을 추론한다. 배열이 정의된 영역을 벗어나면 타입스크립트는 배열을 더 이상 확장할 수 없도록 최종 타입을 할당한다.
튜플은 배열의 서브 타입이다. 튜플은 길이가 고정되어있고, 각 인덱스틔 타입이 알려진 배열의 일종이다. 다른 타입과 달리 튜플은 선언할 때 타입을 명시해야 한다.
let a: [number] = [1]
let b: [string, string, number] = ['a', 'b', 1234]
b = ['c', 'd', 'e', 1234] // Error TS2322: 'string'은 'number' 타입에 할당할 수 없음
튜플은 선택형 요소도 지원한다. 객체 타입에서와 마찬가지로 ?
는 선택형을 뜻한다.
let trainFares: [number, number?][] = [
[3.75],
[8.25, 7.70],
[10.50],
];
튜플이 최소 길이를 갖도록 지정할 때는 나머지 요소를 사용할 수 있다. 튜플은 이형 배열을 안전하게 관리할 뿐 아니라 배열 타입의 길이도 조절한다.
let friends: [string, ...string[]] = ['a', 'b', 'c', 'd'];
// 이형 배열
let list: [number, boolean, ...string[]] = [1, false, 'a', 'b', 'c']
일반적으로 배열은 가변인 반면, 상황에 다라서는 불변인 배열이 필요할 수 있다. 타입스크립트는 readonly
배열 타입을 기본으로 지원하므로 이를 이용해 불변 배열을 바로 만들 수 있다. 배열의 원본을 건드리는 메서드는 사용할 수 없지만, 그렇지 않는 메서드는 사용이 가능하다.
let as: readonly number[] = [1, 2, 3] // readonly number[]
let bs: readonly number[] = as.concat(4) // readonly number[]
타입스크립트는 읽기 전용 배열과 튜플을 만드는 긴 형태의 선언 방법을 지원한다.
// normal array
type A = readonly string[]
type B = ReadonlyArray<string>
type C = Readonly<string[]>
// tuple array
type D = readonly [number, string]
type E = Readonly<[number, string]>
자바스크립트는 null
undefined
두 가지 값으로 부재를 표현한다. 타입스크립트도 두 가지 값 모두를 지원한다. 그 외에 void
, never
타입도 제공한다.
void
는 명시적으로 아무것도 반환하지 않는 함수의 반환을 가리키며, never
는 절대 반환하지 않는 함수 타입을 가리킨다.
// number 또는 null을 반환하는 함수
function a(x: number) {
if (x < 10) return x;
return null;
}
// undefined를 반환하는 함수
function b() {
return undefined;
}
// void를 반환하는 함수
function c() {
let a = 2 + 2;
let b = a * a;
}
// never를 반환하는 함수
function d() {
throw TypeError('I always error');
}
// never를 반환하는 함수
function e() {
while (true) {
doSomething()
}
}
함수 c
는 return
문을 사용하지 않았으므로 void
를 반환한다고 말할 수 있다. 함수 d
는 예외를 던지고, 함수 e는 영원히 실행 되며 반환하지 않으므로 반환 타입이 never
라 할 수 있다.
unknown
이 모든 타입의 상위 타입이라면 never
는 모든 타입의 서브 타입이다. 즉, 모든 타입에 never
를 할당할 수 있으며 never
값은 어디서든 안전하게 사용할 수 있다.
열거형은 해당 타입으로 사용할 수 있는 값을 열거하는 기법이다. 열거형은 키를 값에 할당하는 순서가 없는 자료구조다. 열거형은 두 가지 형식이 있는데, 문자열에서 문자열로 매핑하는 방법, 문자열에서 숫자로 매핑하는 방법이다.
enum Language {
English,
Spanish,
Russian
}
위의 열거형은 사실 아래와 같다.
enum Language {
English = 0,
Spanish = 1,
Russian = 2
}
위의 예시처럼 열거형 멤버에 명시적으로 값을 할당하는 습관을 기르는 것이 좋다.
계산된 프로퍼티를 통해 열거형의 값을 할당할 수 있다.
enum Language {
English = 100,
Spanish = 200 + 300,
Russian // 500의 다음인 501로 추론한다.
}
열거형에 문자열 값을 사용하거나 문자열과 숫자 값을 혼합할 수 있다.
enum Color {
Red = '#c10000',
Blue = '#007ac1',
Pink = 0xc10050,
White = 255
}
타입스크립트에서는 값이나 키로 열거형에 접근할 수 있도록 허용하지만, 이는 불안정한 결과를 초래한다.
let a = Color.Red // Color
let b = Color.Green // Error TS2339: 'Green' 프로퍼티는 'typeof Color' 타입에 존재하지 않음
let c = Color[255] // string
let d = Color[6] // string
Color[6]
은 접근할 수 없어야 하지만 타입스크립트는 접근을 허용한다. 더 안전한 const enum
을 이용하면 타입스크립트가 이런 안전하지 않은 작업을 막도록 만들 수 있다.
const enum Language {
English,
Spanish,
Russian
}
let a = Language.English // Language
let c = Language[0] // Error TS2476: const enum 멤버는 문자열 리터럴로만 접근할 수 있음
let d = Language[6] // Error TS2476: const enum 멤버는 문자열 리터럴로만 접근할 수 있음
const enum
은 역방향 찾기를 지원하지 않으므로 열거형의 동작은 일반 자바스크립트 객체와 비슷해진다. 또한 const enum
은 기본적으로 아무 자바스크립트도 생성하지 않으며 그 대신 필요한 곳에 열거형 멤버의 값을 채워 넣는다.
열거형을 사용할 때에는 값에 숫자 대신 문자열을 넣는 것이 더 안전하다. 이유는 아래의 코드와 같다.
const enum Flippable {
Burger,
Chair,
Cup,
Skateboard,
Table
}
function flip(f: Flippable) {
return 'flipped it';
}
flip(Flippable.Chair) // flipped it
flip(Flippable.Cup) // flipped it
flip(12) // flipped it
Chair
와 Cup
가 예상대로 동작하고 괜찮아 보이지만 모든 숫자를 열거형에 할당할 수 있음을 알게 된다. 이 문제는 문자열 값을 갖는 열거형을 사용해 해결할 수 있다.
const enum Flippable {
Burger = 'Burger',
Chair = 'Chair',
Cup = 'Cup',
Skateboard = 'Skateboard',
Table = 'Table'
}
function flip(f: Flippable) {
return 'flipped it';
}
flip(Flippable.Chair) // flipped it
flip(Flippable.Cup) // flipped it
flip(12) // Error TS2345: '12'인수 타입은 'Flippable' 매개변수 타입에 할당할 수 없음
flip('Hat') // Error TS2345: 'Hat'인수 타입은 'Flippable' 매개변수 타입에 할당할 수 없음
결과적으로 숫자 값을 받는 열거형은 전체 열거형의 안전성을 해칠 수 있다.