본 글은 Typescript Programming을 요약한 글입니다.
자세한 내요은 본 책을 읽으시길 바랍니다.
  • 타입 : 값과 이 값으로 할 수 있는 일의 집합

어떤 값이 T 타입이라면, 이 값을 가지고 어떤 일을 할 수 있고 어떤 일을 할 수 없는지도 알 수 있다. 여기서 중요한 점은 타입검사기를 이용해 유요하지 않은 동작이 실행되는 일을 예방하는 것이다.

3.1 타입을 이야기하다

fuction squareOf(n : number){
	return n*n
}
squareOf(2) // 4
squareOf('z') // 에러 TS2345: "z" 라는 타입의 인수는 'number'타입의 매개변수에 할당할 수 없음

이 예제 코드에서 다음을 알 수 있었다.

  1. squareOf의 매개변수 n은 number로 제한된다.
  2. 2 값은 number에 할당할 수 있는 타입이다.

일단 타입을 제한하면 타입스크립트가 함수를 호출할 때 호환이 되는 인수로 호출했는지 판단한다. 이를 경계의 개념으로 해석할 수도 있다. 타입스크립트에 n의 상위 한정값이 number라고 알려주면 squareOf에 전달하는 모든 값이 number 이하여야 한다. 만약 number 이상의 것(number 또는 문자열이 될 수도 있는 값)이라면 n에 할당할 수 없게 된다.

3.2 타입의 가나다

타입스크립트가 지원하는 각각의 타입을 살펴보면서 각 타입이 무엇을 포함할 수 있는지, 어떤 동작을 수행할 수 있는 지 알아보자. 타입 별칭, 유니온 타입, 인터섹션 타입 등 여러 가지 언어 기능도 확인한다.

3.2.1 any

타입스크립트에서는 컴파일 타임에 모두가 타입이 있어야 하므로 프로그래머와 타입스크립트 둘 다 타입을 알 수 없는 상황에서는 기본 타입인 any라고 가정한다. any는 모든 값의 집합이므로 any는 모든 것을 할 수 있다. 되도록이면 any를 피하고 반드시 최후의 수단으로만 사용하자.

  • TSC 플래그 : nolmplitcitAny
    타입스크립트의 기본 설정은 자유를 허용하므로 any로 추론되는 값을 발견하더라도 예외를 발생시키지 않는다. 그러니 암묵적인 any가 나타났을 때 예외를 일으키고 싶다면 tsconfig.json 파일에서 noImplicitAny 플래그를 활성화하자. 그러나 이는 strict 패밀리에 속하기에 strict를 활성화했다면 따로 설정하지 않아도 된다.

3.2.2 unknown

 타입을 미리 알 수 없는 어떤 값이 있을 떄 any 대신 unknown을 사용하자. any처럼 unknown도 모든 값을 대표하지만, unknown의 타입을 검사해 정제하기 전까지는 타입스크립트가 unknown 타입의 값을 사용할 수 없게 강제한다. 비교연산(==, ===, ||, &&, ?)과 반전(!)을 지원하고 자바스크립트의 typeof, instanceof 연산자로 정제할 수 있다.

let a : unknown = 30 // unknown
let b : a === 123
let c = a + 10 // 에러 TSC2571 : 객체의 타입이 'unknown'임
if(typeof a === 'number'){
	let d = a + 10 // number
}
  1. 타입스크립트가 무언가의 타입을 unknown이라고 추론하는 상황은 없다. 명시적으로 설정해야 한다.
  2. unknown 타입이 아닌 값과 unknown 타입인 값을 비교할 수 있다.
  3. 하지만 unknown 값이 특정 값이라고 가정하고 해당 타입에서 지원하느 동작을 수행할 수 없다. 먼저 해당값이 특정 타입임을 증명해야한다.

3.2.3 boolean

boolean 타입은 true, false 두 개의 값을 갖는다. 비교 연산과 반전 연산만을 사용가능하다.

let a = true // boolean
var b = false // boolean
const c = true // true
let d : boolean = true // boolean
let e : true = true // true
let f : true = false // 에러 TS2322: 'false' 타입을 'true' 타입에 할당할 수 없음
  1. 어떤 값이 boolean인지 타입스크립트가 추론하게 한다.
  2. 어떤 값이 특정 boolean인지 타입스크립트가 추론하게 한다.
  3. 값이 boolean임을 명시적으로 타입스크립트에 알린다.
  4. 값이 특정 boolean임을 명시적으로 타입스크립트에 알린다.

실제 프로그래밍에서는 보통 첫 번째와 두 번째 방법을 사용한다. 두번째와 네번째 방법은 값을 타입으로 사용하므로 e와 f에 사용할 수 있는 값은 boolean 타입이 가질 수 있는 값 중 특정한 하나의 값으로 한장된다. 이 기능을 타입 리터럴이라 부른다.

  • 타입 리터럴 : 오직 하나의 값을 나타내는 타입

const를 사용함으로 타입스크립트는 그 변수의 값이 절대 변하지 않으리라는 사실을 알게 되어 해당 변수가 가질 수 있는 가장 좁은 타입으로 추론한다. 타입 리터럴은 모든 곳에서 일어날 수 있는 실수를 방지해 안전성을 추가로 확보해주는 강력한 언어 기능이다.

3.2.4 number

number 타입은 모든 숫자의 집합이다. 덧셈, 뺄셈, 모듈로(%), 비교 등의 숫자 관련 연산을 수행할 수 있다.

let a = 1234 // number
var b = Infinity * 0.10 // number
const c = 5678 // 5678
let d = a < b // boolean
let e : number = 100 // number
let f : 26.218 = 26.218 // 26.218
let g : 26.218 = 10 // 에러 TS2322 : '10' 타입을 '26.218' 타입에 할당할 수 없음
  1. 어떤 값이 number인지 타입스크립트가 추론하게 한다.
  2. const를 이용해 타입스크립트 값이 특정 number임을 추론하게 한다.
  3. 값이 number임을 명시적으로 타입스크립트에 알린다.
  4. 값이 특정 number임을 명시적으로 타입스크립트에 알린다.

boolean 방식처럼 개발자들은 대개 타입스크립트가 number 타입을 추론하도록 만든다. number 타입임을 명시해야 하는 상황은 거의 없다.

3.2.5 bigint

새로 추가된 타입으로 라운딩 관련 에러 걱정 없이 큰 정수를 처리할 수 있다. number는 253까지의 정수를 표현하지만 bigint를 이용하면 이보다 큰 수도 표현할 수 있다. 덧셈, 뺄셈, 곱셈, 나눗셈, 비교 등의 연산을 지원한다.

let a = 1234n // bigint
const b = 5678n // 5678n
var c = a + b // bigint
let d = a < 1235 // boolean
let e = 88.5n // 에러 TS1353 : bigint 리터럴은 반드시 정수여야 함
let f : bigint = 100n // bigint
let g : 100n = 100n // 100n
let h : bigint = 100 // 에러 TS2322 : '100'타입은 'bigint'타입에 할당 할 수 없음

boolean과 number처럼 bigint타입도 네가지 방법으로 선언할 수 있으며 가능하다면 타입스크립트가 bigint 타입으로 추론하도록 두는 것이 좋다

3.2.6 string

string은 모든 문자열의 집합으로 연결(+), 슬라이스(.slice) 등의 연산을 수행할 수 있다.

let a = 'hello' // string
var b = 'billy' // string
const c = '!' // '!'
let d = a + '' + b + c // string
let e : string = 'zoom' // string
let f : 'john' = 'john' // 'john'
let g : 'john' = 'zoe' 에러 TS2322 : "zoe" 타입을 "john"타입에 할당할 수 없음

boolean과 number처럼 string타입도 네가지 방법으로 선언할 수 있으며 가능하다면 타입스크립트가 string타입으로 추론하도록 두는 것이 좋다

3.2.7 symbol

symbol은 ES2015에 새로 추가된 기능이다. 실무에서는 자주 사용하지 않는 편이며 객체와 맵에서 문자열 키를 대신하는 용도로 사용한다. 사람들이 잘 알려진 키만 사용하도록 강제할 수 있으므로 키를 잘못 설정하는 실수를 방지한다. 객체의 기본 반복자를 설정하거나 객체가 어떤 인스턴스인지를 런타임 오버라이딩 하는 것과 비슷한 기능을 제공한다. symbol타입으로 할 수 있는 동작은 그렇게 많지 않다.

let a = Symbol('a') //symbol
let b : symbol = Symbol('b') // symbol
var c = a === b //boolean
let d = a + 'X' // 에러 TS2469" '+' 연산을 'symbol' 타입에 적용할 수 없음

 만들어진 symbol은 고유하여 다른 symbol과 == 또는 ===로 비교했을 때 같지 않다고 판단된다. symbol도 symbol 타입으로 추론되거나 아니면 멍시적으로 unique symbol을 정의할 수 있다.

const e = Symbol('e') // typeof e
const f : unique symbol = Symbol('f') // typeof f
let g : unique symbol = Symbol('f') // 에러 TS1332 : 'unique symbol' 타입은 반드시 const여야 함
let g = e === e // boolean
let i = e ===f // 에러 TS2367 : 'unique symbol'타입은 서로 겹치는 일이 없으므로 이 비교문의 결과는 항상 'false'

unique symbol도 결국 1, true, 'literal' 등 다른 리터럴 타입과 마찬가지로 특정 symbol를 나타내는 타입이다.

3.2.8 객체

객체 타입은 객체의 형태를 정의한다. 객체 타입({ })만으로 만든 간단한 객체와 복잡한 객체(new로 만든)를 구분할 수 없다. 이는 자바스크립트가 구조 기반 타입을 갖도록 설계되었기 때문이다. 따라서 타입스크립트도 이름 기반 타입 보다 이를 선호한다.

  • 구조 기반 타입화 : 객체의 이름에 상관없이 객체가 어떤 프로퍼티를 갖고 있는지를 따진다.
let a : object = {
	b : 'x'
}

a.b // 에러 TS2399 : 'b' 프로퍼티는 object에 존재하지 않음

object는 서술하는 값에 관한 정보를 거의 알려주지 않으며, 값 자체가 자바스크립트 객체이자 null이 아니라고만 말해줄 뿐이다.

let a = {
	b : 'x'
} // {b : string}
a.b // string

let a : {b : number} {
	b : 12
} // {b : number}

위는 객체 리터럴 문법이다. 타입스크립트가 a의 형태를 추론하게 하거나 중괄호 안에서 명시적으로 타입을 묘사할 수 있다.

  • 객체를 const로 선언할 때의 타입추론 : 자바스크립트 객체의 값은 바뀔 수 있으며, 타입스크립트도 객체를 만든 . 후필드 값을 바꾸려 할수 있다는 사실을 알기에 다른 기본타입과 달리 더 좁은 타입으로 추론하지 않는다.
let a : {b : nmumber}
a = {} // 에러 TS2741 : '{}'타입에는 {b : nmumber} 타입에 필요한 'b'가 없음

a = {
	b : 1,
    c : 2 // 에러 TS2322 : '{b : number; c : number}'타입을 {b : nmumber}에 지정할 수 없음
}
  • 확싫한 할당 : 변수를 선언하고 나중에 초기화 하는 상황에서 타입스크립틑 변수를 사용하기 전에 값을 할당하도록 강제한다.

타입스크립트는 객체 프로퍼티에 엄격한 편이다. 예를 들어 객체에 number 타입의 b라는 프로퍼티가 있어야 한다고 정의하면 b가 없거나 다른 추가 프로퍼티가 있으면 에러를 발생시킨다. 

let a : {
	b : number
    c? : string
    [key : number] : boolean
}
  1. a는 number 타입의 프로퍼티 b를 포함한다.
  2. a는 string 타입의 프로퍼티 c를 포함할 수도 있다.
  3. a는 boolean 타입의 값을 갖는 number 타입의 프로퍼타를 여러 개를 포함할 수 있다.
a = {10 :true} // 에러 TS2741 : {10 : true} 타입에는 'b' 프로퍼티가 없음
a = {b : 1, 33 : 'red'}  // 에러 TS2741 : 'string'타입은 'boolean' 타입에 할당할 수 없음
  • 인덱스 시그니쳐 : [key : T] : U 와 같은 문법은 타입스크립트에 어떤 객체가 여러 키를 가질 수 있음을 알려준다. 명시적으로 정의한 키 외에 다양한 키를 객체에 안전하게 추가할 수 있다. 인덱스 시그니쳐의 키 T 는 반드시 number나 string 타입에 할당할 수 있는 타입이어야한다. 키 이름은 원하는 이름을 가져다 바꿔도 된다. 예시) let airplaneSeatingAssignments : {[seatNumber : string] : string}

 필요한 경우 readonly 한정자를 이용해 특정 필드를 읽기 전용으로 정의할 수 있다. 객체 리터럴 표기법에는 빈 객체 타입({})이라는 특별한 상황이 존재한다. null과 undefined를 제외한 모든 타입은 빈 객체 타입에 할당할 수 있으나 사용하기 까다롭기에 피하는 것이 좋다.

 마지막으로 객체 : Object로 객체 타입을 만드는 방법도 있다. {}과 비슷한 방법이며 마찬가지로 가능하면 사용하지 않아야 한다. 객체를 정의하는 방법은 다음과 같이 네 가지로 요약할 수 있다.

  1. 객체 리터럴 또는 형태라 불리는 표기법({a : string}). 객체가 어떤 필드를 포함할 수 있는지 알고 있거나 객체의 모든 값이 같은 타입을 가질 떄 사용한다.
  2. 빈 객체 리터럴 표기법({}). 이 방법믄 비추천한다.
  3. object 타입. 어떤 필드를 가지고 있는지는 관심 없고, 그저 객체가 필요할 때 사용한다.
  4. Object 타입. 이 방법은 비추천한다.

3.2.9 타입 별칭, 유니온, 인터섹션

값 뿐만 아니라 타입에도 어떤 동작을 수행 할 수 있다.

타입 별칭

변수를 선언해서 값 대신 변수로 칭하듯이 타입 별칭으로 타입을 가리킬 수 있다.

type Age = number
type Person = {
	name : string
    age : Age
}

 타입스크립트는 별칭을 추론하지 않으므로 반드시 별칭의 타입을 명시적으로 정의해야한다.

let age : Age = 55
let driver : Person = {
	name : 'James May',
    age : age
}

Age는 number의 별칭이므로 number에도 할당할 수 있다.

let age = 55
let driver : Person = {
	name : 'James May',
    age : age
}

정리하면 위 코드처럼 바꿀 수 있다.

 자바스크립트 변수 선언과 마찬가지로 하나의 타입을 두번 정의할 수는 없다. let과 const처럼 타입 별칭에도 블록 영역이 적용된다. 내부에 정의한 타입 별칭이 외부의 정의를 덮어쓴다. 타입 별칭은 복잡한 타입을 DRY하지 않도록 해주며 변수가 어떤 목적으로 사용되었는지 쉽게 이해할 수 있게 도와준다. 값을 변수로 할당할지를 결정하는 것과 같은 기준으로 타입 별칭을 사용할지 여부를 결정할 수 있다.

유니온과 인터섹션 타입

 타입스크립트는 타입에 적용할 수 있는 특별한 연산자인 유니온(|)과 인터섹션(&)을 제공한다. 타입은 집합과 비슷하므로 집합처럼 연산을 수행할 수 있다.

type Cat = {name : string, purrs : boolean}
type Dog = {name : string, bark : boolean, wags : boolean}
type CarOrDogOrBothh = Cat | Dog
type CatAndDog = Cat & Dog

// Cat
let a : CarOrDogOrBoth = {
	name : 'Bonkers',
    purrs : true
}

// Both
a = {
	name : 'Donkers',
    barks : true,
    purrs : true,
    wags : true
}

let b : CatAndDog = {
	name : 'Domino',
    barks : true,
    purrs : true,
    wags : true
}

 유니온 타입 (|)에 사용된 값이 꼭 유니온을 구성하는 타입 중 하나일 필요는 없으며 양쪽 모두에 속할 수 있다.

실전에서는 대개 인터섹션보다 유니온을 자주 사용한다.

function trueOrNull(isTrue : boolean) {
	if(ifTrue){
    	return 'true'
    }
    return null
}

이 함수는 string 또는 null을 반환할 수 있다. 이를 다음처럼 표현할 수 있다.

type Returns = string | null

다음 함수 예제를 보자

function(a : string, b : number){
	return a || b
}

조건이 참이면 반환타입이 string이고 그렇지 않으면 number다. 즉, string | number를 반환한다.

3.2.10 배열

 타입스크립트 배열도 연결, 푸시, 검색, 슬라이스 등을 지원하는 특별한 객체이다.

let a = [1,2,3] // number[]
var b = ['a','b'] // string[]
let c : string[] = ['a'] // string[]
let d = [1, 'a'] // (numbner | string)[]
const e = [2, 'b'] // (numbner | string)[]

let f = ['red']
f.push('blue')
f.push(true) // 에러 TS2345 : 'true' 타입 인수를 'string' 타입 매개변수에 할당할 수 없음

let g = [] // any[]
g.push(1) // number[]
g.push('red') // (string | number)[]

let h : number[] = [] // number[]
h.push(1) // number[]
h.push('red') // 에러 TS2345 : 'red' 타입 인수를 'number' 타입매개변수에 할당할 수 없음

  대개 배열을 동형으로 만든다. 즉.  한 배열에 모든 항목이 같은 타입을 갖도록 설계하려 노력한다. 그렇지 않으면 타입스크립트에 배열과 관련한 작업이 안전한지 증명해야 하므로 추가 작업을 해야 한다.

 예제 f를 보면 왜 동열 배열의 처리가 쉬운지 알 수 있다. 배열을 선언하고 문자열 타입의 값을 추가했을 때 타입스크립트는 이 배열이 문자 값을 갖는 배열이라 추론한다. 'blue'는 문자열이므로 아무 문제없이 추가되지만 true를 추가하려하면 에러가 발생한다. 반면 d는 초기화하면서 number와 string을 저장했으므로 타입스크립트는 d의 타입을 number | string으로 추론한다.객체와 마찬가지로 배열을 const로 만들어도 타입스크립트는 타입을 더 좁게 추론하지 않는다.

 g는 특별한 상황으로, 빈 배열로 초기화 하면 타입스크립트는 배열의 요소타입을 알 수 없으므로 any일 것으로 추측한다. 배열을 조작하여 요소를 추가하면 타입스크립트가 주어진 정보를 이용해 배열의 타입을 추론한다. 배열이 정의된 영역을 벗어나면(예 : 함수 안에서 배열을 선언하고 이를 반환) 타입스크립트는 배열을 더 이상 확장할 수 없도록 최종타입을 할당한다.

3.2.11 튜플

 튜플은 길이가 고정되어 있고, 각 인덱스의 타입이 알려진 배열의 일종이다. 다른 타입과 달리 튜플은 선언할 떄 타입을 명시해야 한다. 자바스크립트에서 배열과 튜플에 같은 대괄호을 사용하는데 타입스크립트에서는 대괄호를 배열 타입으로 추론하기 때문이다.

let a : [number] = [1]

// [이름, 성씨, 생년] 튜플
let b : [string, string, number] = ['malcolm','gladwell',1963]
b = ['queen', 'elizabeth', 'ii', 1962] // 에러 TS2322 : 'string'은 'number'타입에 할당할 수 없음

튜플은 선택형 요소도 지원한다. 객체 타입에서와 마찬가지로 ?는 선택형을 뜻한다.

// 방향에 따라 다른 값을 갖는 기차 요금 배열
let trainFares : [number, number?][] = [
	[3.75],
    [8.75, 7.70],
    [10.50]
]

// 다음과 같음
let moreTrainFares : ([number] | [number, number])[] = [
 // ...
]

 또한 튜플이 최소 길이를 갖도록 지정할 때는 나머지 요소(...)를 사용할 수 있다.

let friends : [string, ...string[]] = ['Sara', 'Tail', 'Chloe', 'Claire']
// 이형 배열
let list : [number, boolean, ...string[]] = [1, false, 'a', 'b', 'c']

 

읽기 전용 배열과 튜플

 타입스크립트는 readonly 배열 타입을 기본을 지원하므로 이를 이용해 볼변 배열을 바로 만들 수 있다. 읽기 전용 배열은 명시적 타입 어노테이션으로 만들 수 있다. 읽기 전용 배열을 갱신하려면 .push, .slice처럼 내용을 바꾸는 동작 대신 .concat, .slice같이 내용을 바꾸지 않는 메서드를 사용해야 한다.

let as : readonly number[] = [1,2,3] // readonly number[]
let bs : readnoly numner[] = as.concat(4) // readonly number[]
let three = bs[2] // number
as[4] = 5 // 에러 TS2542 : 'readonly number[]'의 인덱스 시그니쳐 타입은 읽기만 허용함
as.push(6) // 에러 TS2339 : 'push' 프로퍼티는 'readonly number[]' 타입에 존재하지 않음

타입스크립트는 Array처럼 읽기 전용 배열과 튜플을 만드는 긴 형태의 선언 방법을 지원한다.

type A = readonly string[] // readonly string[]
type B = ReadonlyArray<string> // readonly string[]
type C = Readonlt<string[]> // readonly string[]

type D = readonly [number, string] // readonly [number, string]
type E = Readonly<[number, string]> // readonly [number, string]

읽기 전용 배열은 스프레드(...)나 .slice 등으로 배열을 조금만 바꿔도 우선 배열을 복사해야 하므로, 주의하지 않으면 응용 프로그램의 성능이 느려질 수 있다.

3.2.12 null, undefined, void, never

 자바스크립트는 null, undefined 두 가지 값으로 부재를 표현한다. 타입스크립트도 두 가지 값 모두를 지원한다. 타입스크립트에서 undefined 값의 타입은 오직 undefined 뿐이고 null 값의 타입은 null 뿐이라는 점에서 특별한 타입이다. 두 값은 조금 다른데 undefined는 아직 정의하지 않았음을 의미하는 반면 null은 값이 없다는 의미다. 

 타입스크립트는 이외에도 void와 never 타입도 제공한다. void는 명시적으로 아무것도 반환하지 않는 함수의 반환타입을 가리키며 never는 절대 반환하지 않는 함수 타입을 가리킨다.

// (a) nnumber 또는 null을 반환하는 함수
function a (x : number){
	if(x<10){
    	return x
    }
    return null
}

// (b) undefined를 반환하는 함수
function b() {
	return undefined
}

// (c) void를 반환하는 함수
function c() {
	let a = 2 +2
    let b = a * a
}

// (d) never를 반환하는 함수
function d() {
	throw TypeError('I always error')
}

// (e) never를 반환하는 함수
function e() {
	while (true) {
    	dosomething()
    }
}
  1. a은 null를 , b는 undefined를 명시적으로 반환한다.
  2. c는 undefined를 반환하지만 명시적인 return문을 사용하지 않았으므로 void를 반환한다고 말할 수 있다.
  3. d는 예외를 던진다
  4. e는 영원히 실행되며 반환하지 않았므로 반환타입이 never라 할 수 있다.

unknown이 모든 타입의 상위 타입이라면 never는 모든 타입의 서브타입이다. 즉, 모든 타입에 never를 할당할 수 있으며 never 값은 어디서든 안전하게 사용할 수 있다.

타입 의미
null 값이 없음
undefined 아직 값을 변수에 할당하지 않음
Void return문을 포함하지 않는 함수
never 절대 반환하지 않는 함수
  • 엄격한 null 확인
    예전 버전의 타입스크립트 또는 strictNullCheck 옵션을 false인 경우에서는 null이 조금 다르게 작동한다. 이때 null은 never를 제외한 모든 타입의 하위 타입이다. 즉, 모든 타입이 null이 될 수 있으므로 모든 값이 null인지 아닌지 먼저 확인하지 않고는 타입이 무엇이라고 단정할 수 없다. 실무에서는 이는 굉장히 불편한 일이므로 보통 이과정을 생략한다. 그리고 예상치 않은 상황에서 값이 null이라면 런타임에 치명적인 널 포인터 예외가 발생한다.

3.2.13 열거형

열거형은 해당 타입으로 사용할 수 있는 값을 열거하는 기법이다. 열거형은 키를 값에 할당하는, 순서가 없는 자료구조다. 키가 컴파일 타임에 고정된 객체라고 생각하면 쉽다. 따라서 타입스크립트는 키에 접근할 떄 주어진 키가 실제 존재하는 지 확인할 수 있다.

enum Language {
	English,
    Spanish,
    Russain
}

타입스크립트는 자동으로 열거형의 각 멤버에 적절한 숫자를 추론해 할당하지만, 값을 명시적으로 설정할 수도 있다.

enum Language {
	English = 0,
    Spanish = 1,
    Russain = 2
}

점 또는 괄호 표기법을 열거형 값에 접근할 수 있다.

let myFirstLanguage = Language.Russian // Language
let mySecondLanguage = Language['English'] // Language

열거형을 여러 개로 나눠 저의한 다음 타입스크립트가 이들을 합치도록 할 수도 있다. 타입스크립트는 여러 열거형 정의 중 한 가지 값만 추론할 수 있으므로 열거형을 분할할 때 주의해야 하며, 각 열거형 멤버에 명시적을 값을 할당하는 습관을 기르는 것이 좋다. 계산된 값을 사용할 수도 있으므로 모든 값을 정의할 필요는 없다.(빠진 값은 타입스크립트가 추론한다.)

enum Language {
	English = 100,
    Spanish = 200 + 300,
    Russain // 501로 추론
}

열거형에 문자열 값을 사용하거나 문자열과 숫자 값을 혼합할 수 있다. 타입스크립트에서는 값이나 키로 열거형에 접근할 수 있도록 허용하지만 이는 불안정한 결과를 초래하기 쉽다.

let a = Color.Red // Color
let b = Color.Green // 에러 TS2339 : 'Green' 프로퍼티는 'typeof Color'타입에 존재하지 않음

let c = Color[255] // stirng
let d = Color[6] // string !!! << 에러가 발생하지 않고 실행된다

더 안전한 열거형 타입인 const enum을 이용하면 타입스크립트가 이런 안전하지 않은 작업을 막도록 만들 수 있다.

const enum Language {
	English ,
    Spanish ,
    Russain 
}

// 유요한 enum 키 접근
let a = Language.English // Language

// 유요하지않은 enum 키 접근
let b = Language.Taglog // 에러 TS2339: 'Taglog' 프로퍼티는 'typeof Language'타입에 존재하지 않음

// 유요한 enum 키 접근
let c = Language[0] // 에러 TS2476: const enum 멤버는 문자열 리터럴로만 접근할 수 있음

// 유요하지 않은 enum 키 접근
let d = Language[6] // 에러 TS2476: const enum 멤버는 문자열 리터럴로만 접근할 수 있음

const enum은 기본적으로 아무 자바스크립트도 생성하지 않으며 그 대신 필요한 곳에 열거형 멤버의 값을 채워 넣는다. (타입스크립트는 Language.Spanish가 사용된 모든 코드를 값 1로 바꾼다.)

  • TSC 플래그 : preserveConstEnums
    누군가의 타입스크립트 코드에 정의된 const enum을 가져왔을 때는 이 채워 넣기 기능이 문제를 일으킬 수 있다. 개발자가 타입스크립트 코드를 컴파일한 이후에 열거형을 만든 사람이 자신의 const enum을 갱신하면 런타임에 같은 열거형이 버전에 따라 다른 값을 갖게 되고, 타입스크립트가 이 상황에서 할 수 있는 일은 없다.
    const enum을 사용할 때는 채워 넣기 기능을 되도록 피해야 하며 제어할 수 있는 타입스크립트 프로그램에서만 사용해야한다. NPM으로 배포하거나 라이브러리로 제공할 프로그램에서는 const enum를 사용하지 말아야한다.
    const enum의 런타임 코드 생성을 활성화할려면 tsconfing.json 파일에서 preserveConstEnums TSC 설정을 true로 바꾼다.
const enum Flippable {
	Burger,
    Chair,
    Cup,
    Skateboard,
    Table
}

function flip(f : Filppable) {
	retrun 'flipped it'
}

filp(Flippable.Chair) // 'flipped it'
filp(12) // 'flipped it' !!!! <<< 에러가 발생해야하지만 실행된다

위 예제처럼 타입스크립트 할당규칙 때문에 생긴 운이 나쁜 결과로 에러가 발생하지 않고 실행될 수 있다. 문자열 값을 갖는 열거형을 사용해 해결할 수 있다. 결과적으로 숫자 값을 받는 열거형은 전체 열거형의 안정성을 해칠 수 있다.

const enum Flippable {
	Burger = 'Burger',
    Chair = 'Chair',
    Cup = 'Cup',
    Skateboard = 'Skateboard',
    Table = 'Table'
}

function flip(f : Filppable) {
	retrun 'flipped it'
}

filp(Flippable.Chair) // 'flipped it'
filp(12) // 에러 TS2345: '12' 인수 타입은 'Flippable' 매개변수 타입에 할당할 수 없음
filp('Hat') // 에러 TS2345: 'Hat' 인수 타입은 'Flippable' 매개변수 타입에 할당할 수 없음
  • 열거형을 안전하게 사용하는 방법은 까다로우므로 열거형 자체를 멀리할 것을 권한다. 타입스크립트에는 열거형을 대체할 수단이 많다.

3.3 마치며

타입스크립트가 값의 타입을 추론하도록 하거나 값의 타입을 명시할 수 있다. let과 var를 사용하면 일반적인 타입으로 추론하는 반면, const를 이용하면 더 구체적인 타입을 추론하게 만든다. 구체적 타입은 보통 일반 타입의 서브 타입이다.

타입 서브 타입
Boolean 볼 리터럴
bigint 큰 정수 리터럴
number 숫자 리터럴
string 문자열 리터럴
symbol unique symbol
object 객체 리터럴
Array 튜플
enum const enum
본 글은 Typescript Programming을 요약한 글입니다.
자세한 내요은 본 책을 읽으시길 바랍니다.

 본 장은 타입스크립트 컴파일러(TypeScript Compiler, TSC)의 동작원리, 타입스크립트의 기능 소개, 프로그램 개발에 적용할 수 있는 패턴 등을 소개한다.

2.1 컴파일러

 프로그램은 프로그래머가 작성한 다수의 텍스트 파일로 구성된다. 이 텍스트를 컴파일러라는 특별한 프로그램이 파싱하여 추상 문법 트리(abstract syntax tree, AST)라는 자료구조로 변환한다. 그리고 컴파일러는 다시 AST를 바이트코드라는 하위 수준의 표현으로 변환한다. 이후 런타임이라는 다른 프로그램에 바이트코드를 입력해 평가하고 결괄르 얻을 수 있다.

  1. 프로그램이 AST로 파싱된다.
  2. AST가 바이트코드로 컴파일된다.
  3. 런타임이 바이트코드를 평가한다.

 타입스트립트가 다른 언어와 다른 점은 컴파일러가 코드를 바이트코드 대신 자바스크립트 코드로 변환한다는 점이다. 타입스크립트 컴파일러는 AST를 만들어 결과 코드를 내놓기 전에 타입 확인을 거친다.

  • 타입 검사기 : 코드의 타입 안전성을 검증하는 특별한 프로그램

타입 확인과 자바스크립트 방출 부분을 포함하면 타입스크립트 컴파일 과정은 대략 예시처럼 된다.

  1. 타입스크립트 소스 => 타입스크립트 AST
  2. 타입 검사기가 AST를 확인
  3. 타입스크립트 AST => 자바스크립트 소스 : TS 영역
  4. 자바스크립트 소스 => 자바스크립트 AST
  5. AST => 바이트 코드
  6. 런타임이 바이트코드를 평가 : JS 영역

TSC가 타입스크립트 코드를 자바스크립트 코드로 컴파일할 때는 개발자가 사용한 타입을 확인하지 않는다. 개발자가 코드에 기입한 타입 정보는 최종적으로 만들어지는 프로그램에 아무런 영향을 주지 않으며, 단지 타입을 확인하는 데만 쓰인다는 뜻이다.

2.2 타입 시스템

  •  타입 시스템 : 타입 검사기가 프로그램에 타입을 할당하는 데 사용하는 규칙 집합

 타입 시스템은 보통 두가지 종류로 나뉜다. 어떤 타입을 사용하는 지를 컴파일러에 명시적으로 알려주는 타입 시스템과 자동으로 타입을 추론하는 타입 시스템으로 구분된다. 타입 스크립트는 두 가지 시스템 모두의 영향을 받았다. 즉, 개발자는 타입을 명시하거나 타입스크립가 추론하도록 하는 방식 중에서 선택할 수 있다.

타입스크립트 vs 자바스크립트

타입 시스템 기능 자바스크립트 타입스크립트
타입 결정 방식 동적 정적
타입이 자동으로 변환되는가? O X
언제 타입을 확인하는가? 런타임 컴파일 타임
언제 에러를 검출하는가? 런타임(대부분) 컴파일 타임(대부분)

 

타입은 어떻게 결정되는가?

 동적 타입 바인딩이란 자바스크립트가 프로그램을 실행해야만 특정 데이터의 타입을 알 수 있음을 의미한다. 타입스크립트는 점진적으로 타입을 확인하는 언어다. 즉, 타입스크립트는 컴파일 타임에 프로그램의 모든 타입을 알고 있을 떄 최상의 결과를 보여줄 수 있지만, 프로그램을 컴파일하는 데 반드시 모든 타입을 알아야 하는 것은 아니다.

자동으로 타입이 변환되는가?

 자바스크립트는 약한 언어다. 유효하지 않은 연산을 수행하면 다양한 규칙을 적용해가며 개발자가 정말 의도한 바를 알아내려 노력하고, 주어진 정보로 최상의 결과를 도출한다. 반면 타입스크립트는 유효하지 않은 작업을 발견하는 즉시 불평한다. 같은 자바스크립트 코드를 TSC로 실행하면 다음처럼 에러가 발생한다.

3 + [1] ; // 에러 TS2365 : '+' 연산자를 '3'과 'number[]'타입에 적용할 수 없음
(3).toString() + [1].toString() // "31"로 평가

 올바르지 않아 보이는 연산을 수행하면 타입스크립트가 바로 그 부분을 지적하며, 의도를 명시해야 타입스크립트의 지적을 무사히 통과할 수 있다.

언제 타입을 검사하는가?

자바스크립트는 주어진 상황에서 개발자가 무엇을 의도하는지에 맞춰 변환하려 최대한 노력할 뿐 거의 대부분의 상황에서 타입이 무엇인지 따지지 않는다. 반면 타입스크립트는 컴파일 타임에 코드의 타입을 확인하기 떄문에 코드를 실행하지 않고도 이전 예제 코드에 에러가 있음을 바로 알 수 있다. 타입스크립트는 정적으로 코드를 분석해 이런 에러를 검출하여 코드를 실행하기도 전에 알려준다.

에러는 언제 검출되는가?

자바스크립트는 런타임에 예외를 던지거나 암묵적 형변환을 수행한다. 즉, 프로그램을 실행해야만 어떤 문제가 있음을 확인할 수 있다. 타입스크립트는 컴파일 타임에 문법 에러와 타입 관련 에러를 모두 검출한다. 실제 개발자가 코딩을 시작하면 코드 편집기가 이런 종류의 에러를 바로 보여준다.

2.3 코드 편집기 설정

 TSC 자체도 타입스크립트로 구현된 명령행 도구(이런 이유로 자체 호스팅 컴파일러 또는 자신을 컴파일하는 컴파일러라는 특별한 종류의 컴파일러가 된다)이므로 TSC를 실행하려면 NodeJS가 필요하다. NPM을 이용해 TSC와 TSLint(TypeScript Linter)를 설치한다.

# 새 디렉터리 생성
mkdir chapter-2
cd chapter-2

# 새 NPM 프로젝트 초기화 (프롬프트의 지시에 따름)
npm init

# TSC, TSLint, NodeJS용 타입 선언 설치
npm install --save-dev typescript tslint @types/node

2.3.1 tsconfig.json

모든 타입스크립트 프로젝트는 루트 디렉터리에 tsconfig.json이라는 파일이 존재해야 한다. tsconfig.json 파일은 타입스크립트의 프로젝트에서 어떤 파일을 컴파일하고, 어떤 자바스크립트 버전을 방출하는 지 등을 정의한다.( ./node_modules/.bin/tsc --init 이라는 타입스크립트의 내장 명령을 이용해 자동으로 설정할 수 있다.)

{
	"compilerOptions" : {
    	"lib": ["es2015"],
        "module": "commonjs",
        "outDir": "dist",
        "sourceMap: true,
        "strict": true,
        "target": "es2015"
    },
    "include": [
    	"src"
    ] 
}
옵션 설명
include TSC가 타입스크립트 파일을 찾을 디렉터리
li TSC가 코드 실행 환경에서 이용할 수 있다고 가정하는 API(ES5의 Fuction.prototype.bind, ES2015의 Object.assign, DOM의 document.querySelector 등)
module TSC가 코드를 컴파일할 대상 모듈 시스템(CommonJS, SystemJS, ES2015 등)
outDir 생성된 자바스크립트 코드를 출력할 디렉터리
strict 유효하지 않은 코드를 확인할 떄 가능한 엄격하게 검사함. 이 옵셥을 이용하면 코드가 적절하게 타입을 갖추도록 강제할 수 있다. 이 책에서는 모든 경우에 strict 옵션을 적용하므로 프로젝트에도 이 옵션을 사용하도록 권장한다.
target TSC가 코드를 컴파일할 자바스크립트 버전(ES3, ES5, ES2015 등)

 위 표는 tsconfig.json이 지원하는 옵션 중 일부만 나열했으며 언제든 새로운 옵션을 추가할 수 있다. 실무에서는 이 옵션들을 자주 바꿀 일은 없다. 다만 새로운 번들러를 추가하거나, 브라우저용 타입스크립트를 작성하기 위해 "dom"을 lib에 추가하거나, 자바스크립트 코드를 타입스크립트로 마이그레이션할 때 엄격함의 수준을 조절하는 상황 등에서는 옵션 설정을 바꿔야 한다.

 tsconfig.json 파일을 이용해 간편하게 소스 버전 관리 시스템에 설정을 포함할 수 있고, 명령행을 이용해 TSC의 옵션 대부분을 제어하는 방법도 있다.

2.3.2 tslint.json

보통 프로젝트는 TSLint 설정을 정의하는 tslint.json 파일도 포함된다. ./node_modules/.bin/tslint -init 으로 기본값으로 채워진 파일을 만들 수 있다. 그리고 만들어진 파일을 자신의 코딩 스타일에 맞게 편집할 수 있다.

{
	"defaultSeverity": "error",
    "extends": [
    	"tslint:recommended"
    ],
    "rules" : {
    	"semiconlon": false,
        "trailing-comma": false
    }
}

2.4 index.ts

 tsconfig.json, tslint.json 파일을 설정했으면 타입스크립트 파일을 추가한다.

mkdir src
touch src/index.ts

이후 index 파일에 코드을 입력한다!

  • 이는 백지상태에서 설정해본 경험이 없다고 가정하고 각 단계를 차근차근 설명했다. 다음부터는 프로젝트를 더 빠르게 설정할 수 있는 여러 방법을 이용할 수 있다.
  • ts-node를 설치한다. 이를 이용하면 명령 한 번으로 타입스크립트를 컴파일하고 실행할 수 있다.
  • typescript-node-starter 같은 뼈대 제공 도구를 이용해 프로젝트 디렉터리 구조를 빠르게 생성할 수 있다.

+ Recent posts