타입(type) : 값과 이 값으로 할 수 있는 일의 집합
타입의 가나다anyunknownbooleannumberbigintstringsymbol객체1. 값을 object로 선언하여 객체 서술 — 어떤 필드를 가지고 있는지는 관심 없고, 그저 객체가 필요할 때 사용2. 객체 리터럴 문법 ( 객체 리터럴로 선언하여 자동추론 혹은 명시적으로 {}를 이용한 타입 선언)3. 빈 객체 타입({})이라는 상황 — 사용하지 않는 것이 좋음4. Object 로 객체 타입을 만드는 방법 — 사용하지 않는 것이 좋음타입 별칭(type alias)유니온( | )과 인터섹션( & ) 타입배열튜플읽기 전용 배열과 튜플null, undefined, void, never열거형마치며
타입의 가나다
any
- 뭐든지 할 수 있지만 꼭 필요한 상황이 아니라면 사용하지 않는 것이 좋음. 최후의 보루
- 타입스크립트가 any로 어떤 값을 추론해야 되는 상황이라면 빨간 밑줄 표시됨. any 사용하려면 명시적으로 선언해야 함
TSC 플래그 : noImplicitAny
타입스크립트의 기본 설정은 자유를 허용하므로 any로 추론되는 값을 발견하더라도 예외 발생시키지 않음. 암묵적인 any가 나타났을 때 예외 일으키고 싶으면 tsconfig.json에서 noImplicitAny 플래그 활성화.
strict 활성화하면 포함되어 있음
unknown
- 타입을 미리 알 수 없는 어떤 값이 있을 때 any 대신 unknown을 사용
- any처럼 unknown도 모든 값을 대표하지만 unknown의 타입을 검사해 정제(refine)하기 전까지는 타입스크립트가 unknown 타입의 값을 사용할 수 없게 강제함
- 타입스크립트가 무언가의 타입을 unknown이라고 추론하는 상황은 없다. unknown 타입을 사용하고자 한다면 개발자가 명시적으로 설정해야 함
let a: unknown = 30; let b = a === 123
boolean, number, bigint, string 모두 아래 방법으로 타입 선언이 가능하무
- 값을 통해 타입스크립트가 타입을 추론하게 하는 방법
- 어떤 값이 특정 타입 인지 타입스크립트가 추론하게 하는 방법(const)
- 명시적으로 타입을 선언하는 방법
- 명시적으로 특정 타입 임을 선언하는 방법(type literal)
boolean
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'타입에 할당할 수 없음
- 어떤 값이 boolean인지 타입스크립트가 추론하게 한다( a,b )
- 어떤 값이 특정 boolean 인지 타입스크립트가 추론하게 한다(c)
- const를 사용하여 타입스크립트가 자동으로 변수의 타입을 리터럴로 추론. const이므로 타입스크립트가 그 변수의 값이 절대 변하지 않으리라는 사실을 알게 되어 해당 변수가 가질 수 있는 가장 좁은 타입으로 추론함
- let이냐 const냐에 따라 타입스크립트가 추론하는 타입이 달라짐
- 값이 boolean임을 명시적으로 타입스크립트에 알린다(d)
- 값이 특정 boolean임을 명시적으로 타입스크립트에 알린다(e,f) —
type literal
타입 리터럴 : 오직 하나의 값을 나타내는 타입
타입 리터럴(type literal)
오직 하나의 값을 나타내는 타입
타입 리터럴은 모든 곳에서 일어날 수 있는 실수를 방지해 안전성을 추가로 확보해주는 강력한 언어 기능
프로그래밍 언어 세계에서 타입스크립트를 독특하게 만드는 기능
number
- 모든 숫자(정수, 소수, 양수, 음수, Infinity, NaN 등)의 집합
긴 숫자를 처리할 때는 숫자 분리자를 이용해 숫자를 읽기 쉽게 만들 수 있다. 숫자 분리자는 타입과 값 모두에 사용할 수 있음
let oneMillion = 1_000_000 // 1000000과 같음
let twoMillion = 2_000_000 // 2_000_000
bigint
- 자바스크립트와 타입스크립트에 새로 추가된 타입으로, 이를 이용하면 라운딩 관련 에러 걱정 없이 큰 정수를 처리할 수 있음
- number 는 2**53 까지의 정수를 표현할 수 있고 bigint는 이보다 더 큰 수도 표현 가능함
let a = 1234n; // bigint const b = 5678n; // 5678n var c = a + b; // bigint
string
symbol
객체
- 타입스크립트의 객체(object) 타입은 객체의 형태(shape)를 정의한다.
- 객체 타입만으로는 ({ } 로 만든) 간단한 객체와 (new를 사용해 만든) 복잡한 객체를 구분할 수 없음
- 자바스크립트가 구조 기반 타입(structural type)을 갖도록 설계되었기 때문에. 따라서 타입스크립트도 이름 기반 타입(nominal type) 스타일보다는 자바스크립트 스타일을 선호함
구조 기반 타입화
구조 기반 타입화에서는 객체의 이름에 상관없이 객체가 어떤 프로퍼티를 갖고 있는지를 따진다(이름 기반 타입에서는 이름을 따짐)
일부 언어에서는 덕 타이핑(duck typing. 동적 타이핑의 한 종류)이라고 함(겉표지만 보고 책을 판단하지 않는 것과 같은 원리)
1. 값을 object로 선언하여 객체 서술 — 어떤 필드를 가지고 있는지는 관심 없고, 그저 객체가 필요할 때 사용
let a: object = { b: 'x' } a.b // 에러 TS2339 : 'b' 프로퍼티는 'object'에 존재하지 않음
- object는 any 보다 조금 더 좁은 타입임
- object는 서술하는 값에 관한 정보를 거의 알려주지 않고, 값 자체가 자바스크립트 객체라고(그리고 null이 아니라고 말해줄 뿐임)
2. 객체 리터럴 문법 ( 객체 리터럴로 선언하여 자동추론 혹은 명시적으로 {}를 이용한 타입 선언)
// 자동 타입 추론 let a = { b: 'x' // {b: string} } a.b // string let b = { c: { d: 'f' } } // {c: {d: string}} /// 명시적 타입 묘사 let a: {b: number } = { b : 12 } // {b: number} const a: { b: number } = { b: 12 } // 여전히 {b: number} // 객체 리터럴 문법을 통해서 타입 정의 후, c와 Person 모두 해당 타입을 만족하므로 Person을 c로 // 할당할 수 있음 let c : { firstName : string lastName : string } = { firstName : 'john', lastName : 'barrowman' } class Person { constructor( public firstName: string, // public은 this.firstName = firstName을 단축한 것 public lastName : string ) { } } c = new Person('matt', 'smith');
객체를 const로 선언할 때의 타입 추론
객체를 const로 선언하더라도, 타입 리터럴 처럼 타입을 좁혀서 추론하지는 않음
그 이유는 자바스크립트 객체의 값은 바뀔 수 있으며, 타입스크립트도 객체 만든 후 필드 값을 바꾸려 할 수 있다는 사실을 알기 때문
프로퍼티를 추가하거나 필요한 프로퍼티 제공하지 않을 시 일어나는 일
let a: { b: number } a = {} // 에러 TS2741: '{}' 타입에는 {b: number} 타입에 필요한 'b' 프로퍼티가 없음 a = { b: 1, c: 2 } // 에러 TS2322 : '{b: number; c: number}' 타입을 '{b: number}' 타입에 할당할 수없음
확신한 할당(definite assignment)
위 코드는 먼저 변수(a)를 선언한 다음 값을 초기화한 예시이고, 이 방식은 자바스크립트에서 자주 볼 수 있는 패턴이며 타입스크립트에서도 지원함
변수를 선언하고 나중에 초기화하는 상황에서 타입스크립트는 변수를 사용하기 전에 값을 할당하도록 강제함
어떤 프로퍼티는 선택형이고 예정에 없던 프로퍼티가 추가될 수 있다고 타입스크립트에 알려주는 방법, 인덱스 시그니처
let a: { b: number c?: string [key: number]: boolean } // a 에 할당할 수 있는 객체 타입 a = {b:1} a = {b:1, c: undefined} a = {b:1, c:'d'} a = {b: 1, 10: true} a = {b: 1, 10: true, 20: false} a = {10: true} // 에러 TS2741 : b 프로퍼티가 없음 a = {b: 1, 33: 'red'} // 에러 TS2741 : 'string' 타입은 'boolean' 타입에 할당할 수 없음
- a는 number 타입의 프로퍼티 b를 포함
- a 는 string 타입의 프로퍼티 c를 포함할 수도 있음
- a 는 boolean 타입의 값을 갖는 number 타입의 프로퍼티를 여러 개를 포함할 수 있다
인덱스 시그니처(index signatrue)
-
[key : T] : U
와 같은 문법을 인덱스 시그니처라고 부름
- 타입스크립트에 어떤 객체가 여러 키를 가질 수 있음을 알려줌 ⇒ “이 객체에서 모든 T 타입의 키는 U 타입의 값을 갖는다” 라고 해석할 수 있음
- 인덱스 시그니처를 이용하면 명시적으로 정의한 키 외에 다양한 키를 객체에 안전하게 추가할 수 있음
- 규칙 : 인덱스 시그니처의 키(T)는 반드시 number나 string 타입에 할당할 수 있는 타입이어야 함
- 키 이름은 key 가 아닌 다른 값이어도 상관 없음let airplaneSeatingAssignments : { [seatNumber: string]: string } = { '34D': 'Boris Cherny', '34E': 'Bill Gates' }
readonly 한정자 이용해 특정 필드를 읽기 전용으로 정의
let user: { readonly firstName: string // 객체 프로퍼티에 const를 적용한 효과 } = { firstName: 'abby' } user.firstName // string user.firstName = 'abbey with an e' // 에러 TS2540: 'firstName'은 읽기 전용 프로퍼티이므로 // 할당할 수 없음
3. 빈 객체 타입({})이라는 상황 — 사용하지 않는 것이 좋음
let danger : {} danger = {} danger = {x: 1} danger = [] danger = 2
- null과 undefined를 제외한 모든 타입은 빈 객체 타입에 할당할 수 있으나, 이는 사용하기 까다롭게 만듬
- 따라서 가능한 한 빈 객체는 피하는 것이 좋음
4. Object 로 객체 타입을 만드는 방법 — 사용하지 않는 것이 좋음
타입 별칭(type alias)
- let, const, var로 변수를 선언해서 값 대신 변수로 칭하듯이 타입 별칭으로 타입을 가리킬 수 있음
type Age = number type Person = { name: string age: Age } let age: Age = 55 let driver: Person = { name: 'James May', age: age }
- Age는 number 로 타입 별칭을 이용하면 Person의 형태를 조금 더 이해하기 쉽게 정의할 수 있음
- 타입 별칭은 복잡한 타입을 DRY하지 않도록 해주며 변수가 어떤 목적으로 사용되었는지 쉽게 이해할 수 있게 도와줌
type Color = 'red' type Color = 'blue' // 에러 TS2300: 'Color' 식별자를 중복 정의함 //////////////// type Color = 'red'; let x = Math.random() < .5 if (x) { type Color = 'blue' // 위의 Color 정의를 덮어 씀 let b: Color = 'blue' } else { let c: Color = 'red' }
- 자바스크립트 변수 선언(let, const, var)과 마찬가지로 하나의 타입을 두 번 정의할 수는 없음
- let, const 처럼 타입 별칭도 블록 영역에 적용됨. 모든 블록과 함수는 자신만의 영역을 가지므로 내부에 정의한 타입 별칭이 외부의 정의를 덮어씀(shadowing)
유니온( | )과 인터섹션( & ) 타입
type Cat = {name: string, purrs: boolean} type Dog = {name: string, barks: boolean, wags: boolean} type CatOrDogOrBoth = Cat | Dog type CatAndDog = Cat & Dog // Cat let a: CatOrDogOrBoth = { name: 'Bonkers', purrs: true } // Dog a = { name : 'Domin', barks: true, wags: true } // 둘 다 a = { name: 'Donkers', barks: true, purrs: true, wags: true } // 인터섹션에서는 Cat과 Dog의 모든 프로퍼티를 다 가지고 있음 let b: CatAndDog = { name: 'Domino', barks: true, purrs: true, wags: true } function trueOrNull(isTrue: boolean) { if (isTrue) { return 'true' } return null; } type Returns = string | null; function(a: string, b: number) { return a || b; }; //= > string | number
- 실전에서는 대개 인터섹션보다 유니온을 자주 사용함
배열
타입스크립트는
T[]
와 Array<T>
라는 두 가지 배열 문법을 지원함. 성능, 의미상 두 표현은 같음let a = [1, 2, 3]; // number[] var b = ['a', 'b']; // string[] let c: string[] = ['a']; // string[] let d = [1, 'a'] // (string | number)[] const e = [2, 'b'] // (string | number)[] 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' 타입 매개변수에 할당할 수 없음
- 대개는 배열을 homogeneous로 만듬 (그렇지 않으면) 타입스크립트에 배열과 관련한 작업이 안전한지 증명해야 하므로 추가 작업 해야함
- f는
[’red’]
로 초기화함으로 문자열 값을 갖는 배열이라 추론하게 됨 ⇒ true 추가시 에러 - d는 number, string 저장했으므로
number | string[]
으로 추론됨 ⇒ 요소가 숫자와 문자열 중 한가지 일수 있으므로 요소 사용 전에 확인해야 함
function buildArray() { let a = [] // any[] a.push(1) // number[] a.push('x') // (string | number)[] return a; }
튜플
- 튜플은 배열의 서브타입임
- 길이가 고정되어있고, 각 인덱스의 타입이 알려진 배열의 일종
- 다른 타입과 달리 튜플은 선언할 때 타입을 명시해야 함 (배열과 구분할 수 없음 타입 명시 안하면)
let a: [number] = [1] // [이름, 성씨, 생년] 튜플 let b: [string, string, number] = ['malcolm', 'gladwell', 1963]; b = ['queen', 'elizabeth', 'li', 1926] // 에러 TS2322: 'string'은 'number' 타입에 할당x // 방향에 따라 다른 값을 갖는 기차 요금 배열 let trainFares : [number, number?][] = [ [3.75], [8.25, 7.70], [10.50] ]; // 다음과 같음 let moreTrainFares : ([number] | [number, number])[] = [ // ... ] // 튜플이 최소 길이를 갖도록 지정할 때 Rest element(...)를 사용할 수 있음 let friends: [string, ...string[]] = ['Sara', 'Tali', 'Chloe', 'Claire']; // 이형 배열 let list : [number, boolean, ...string[]] = [1, false, 'a', 'b', 'c'];
- 튜플은 이형(heterogeneous) 배열을 안전하게 관리할 뿐 아니라 배열 타입의 길이도 조절함
- 이런 기능을 잘 활용하면 순수 배열에 비해 안전성을 높일 수 있으므로 튜플 사용을 권장함
읽기 전용 배열과 튜플
- 일반 배열은 가변인 반면, 상황에 따라서는 불변(immutable)인 배열이 필요할 수 있음
- 타입스크립트는 readonly 배열 타입을 기본으로 지원하므로 이를 이용해 불변 배열을 바로 만들 수 있음
- 읽기 전용 배열을 갱신하려면 .push, .splice 처럼 내용을 바꾸는 동작 대신 .concat, .slice 같이 내용을 바꾸지 않는 메서드를 사용해야 함
let as: readonly number[] = [1, 2, 3]; // readonly number[] let bs: readonly number[] = as.concat(4). // readonly number[] let three = bs[2] // number as[4] = 5 // 에러 TS2542: 'readonly number[]'의 인덱스 시그니처 타입은 읽기만 허용함 as.push(6) // 에러 TS2339 : 'push' 프로퍼티는 'readonly number[]' 타입에 존재하지 않음
type A = readonly string[] //readonly string[] type B = ReadonlyArray<string> //readonly string[] type C = Readonly<string[]> //readonly string[] type D = readonly [number, string] // readonly [number, string] type E = Readonly<[number, string]> // readonly [number, string]
- 읽기 전용 배열은 바꿀 수 없으므로 코드를 쉽게 이해할 수 있는 장점이 있지만 결국 자바스크립트 배열로 구현한 것. 즉 스프레드(…) 나 slice() 등으로 배열을 조금만 바꿔도 우선 원래 배열을 복사해야 하므로, 주의하지 않으면 프로그램의 성능이 느려질 수 있음
- 작은 배열에서는 오버헤드가 사소하지만, 큰 배열에서는 눈에 띄게 큰 성능 저하를 일으킬 수 있음
불변 배열을 자주 사용해야 하는 상황이라면 리바이론의 immutable 같은 효율적인 라이브러리를 고려하는 것이 좋다
null, undefined, void, never
타입 | 의미 |
null | 값이 없음 |
undefined | 아직 값을 변수에 할당하지 않음 |
Void | return 문을 포함하지 않는 함수 |
never | 절대 반환하지 않는 함수 |
- 자바스크립트는 null, undefined 두 가지 값으로 부재를 표현함. 타입스크립트도 두 가지 값 모두를 지원함
- 타입스크립트에서는 이 값을 무엇이라 부를까? 똑같이 null과 undefined라고 부름
- 타입스크립트에서 undefined 값의 타입은 오직 undefined 뿐이고, null 값의 타입은 null 뿐이라는 점에서 특별한 타입임
- 두 타입의 의미는 조금 다름.
undefined는 아직 정의하지 않았음
,null은 값이 없다
는 의미임
- void와 never 타입은 존재하지 않음의 특징을 조금 더 세밀하게 분류하는, 정말 특수하고 특별한 용도의 타입임
- void 는 명시적으로 아무것도 반환하지 않는 함수의 반환타입(console.log)을 가리킴
- never는 절대 반환하지 않는(예외를 던지거나 영원히 실행되는) 함수 타입을 가리킴
// (a) number 또는 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(); } }
- c는 undefined를 반환하지만 명시적인 return 문을 사용하지 않았으므로 void를 반환한다고 말할 수 있음
- unknown이 모든 타입의 상위 타입이라면, never는 모든 타입의 서브타입임
- 즉, 모든 타입에 never를 할당할 수 있으며 never 값은 어디서든 안전하게 사용할 수 있다
엄격한 null 확인
- 예전 버전의 타입스크립트(또는, TSC의 strictNullChecks 옵션을 false로 설정한 타입스크립트) 에서는 null이 조금 다르게 동작함
- 이때 null은 never를 제외한 모든 타입의 하위 타입임. 즉, 모든 타입은 null이 될 수 있으므로 모든 값이 null인지 아닌지를 먼저 확인하지 않고는 타입이 무엇이라고 단정할 수 없음
- 예를 들어, pizza 라는 변수를 함수에 전달했고 그 변수에 .addAnchoives라는 메서드를 호출해야한다면, 먼저 pizza가 null인지 확인한 다음에 그 동작을 수행할 수 있음
function addDeliciousFish(pizza: Pizza) { return pizza.addAnchoives() // 잡히지 않은 TypeError: null에서 'addAnchoives'라는 // 프로퍼티를 읽을 수 없음 // strictNullChecks = false로 설정하면 타입스크립트는 다음을 허용 addDeliciousFish(null);
열거형
enum Language { English, Spanish, Russian } // 열거형의 이름은 단수 명사로 쓰고, 첫 문자는 대문자로 하는 것이 관례. 키도 앞 글자를 대문자로 표시 enum Language { English = 0, Spanish = 1, Russian = 2 } // 타입스크립트는 자동으로 열거형의 각 멤버에 적절한 숫자를 추론해 할당하지만, 값을 명시적으로 설정할 수도 있음 let myFirstLanguage = Language.Russian; let mySecondLanguage = Language['English']; // 열거형에 문자열 값을 사용하거나 문자열과 숫자 값을 혼합할 수도 있음 enum Color { Red = '#c10000', Blue = '#007ac1', Pink = 0xc10050, White = 255 }; let red = Color.Red; let pink = Color.Pink; let a = Color.Red; let c = Color[255]; let d = Color[0xc10050]; console.log(a); // '#c10000' console.log(c); // White console.log(d); // Pink Color[6] // 접근할 수 없어야 하지만 타입스크립트는 접근을 허용함 // 더 안전한 열거형 타입인 const enum을 이용하면 타입스크립트가 이런 안전하지 않은 작업을 막도록 // 만들 수 있음
const enum Language { English, Spanish, Russian } let a = Language.English; console.log(a); // 0 let b = Language.Tagalog // 에러 TS2339: 'Tagalog' 프로퍼티는 'typeof Language' 타입에 존재하지 않음 let c = Language[0]; // 에러 TS2476: const enum 멤버는 문자열 리터럴로만 접근가능함
- const enum은 역방향 찾기를 지원하지 않으므로 열거형의 동작은 일반 자바스크립트 객체와 비슷해짐
TSC 플래그 : preserveConstEnums (const enum의 런타임 코드 생성을 활성화하는 옵션)
누군가의 타입스크립트 코드에 정의된 const enum을 가져왔을 때는 이 채워 넣기 기능이 문제를 일으킬 수 있음
const enum을 사용할 때는 채워 넣기 기능은 되도록 피해야 하며 개발자가 직접 제어할 수 있는 타입스크립트 프로그램에서만 사용해야 함. NPM 으로 배포하거나 라이브러리로 제공할 프로그램에서는 const enum을 사용하지 말아야 함
열거형을 안전하게 사용하는 방법은 까다로우므로 열거형 자체를 멀리할 것을 권함. 타입스크립트에는 열거형을 대체할 수단이 많음
마치며
- 타입스크립트는 다양한 내장 타입을 제공함. 타입스크립트가 값의 타입을 추론하도록 하거나 값의 타입을 명시할 수 있음
- let과 var를 사용하면 일반적인 타입으로 추론하는 반면, const를 이용하면 더 구체적인 타입을 추론하게 만듬
- 대부분의 타입은 일반 타입과 구체적 타입 두 가지를 제공하며, 구체적 타입은 보통 일반 타입의 서브타입임
타입 | 서브타입 |
Boolean | 불 리터럴 |
bigint | 큰 정수 리터럴 |
number | 숫자 리터럴 |
string | 문자열 리터럴 |
symbol | unique symbol |
object | 객체 리터럴 |
Array | 튜플 |
enum | const enum |