3장 타입의 모든 것
2021. 07. 23
BeforeEnter
1. 컴파일러
일반적인 프로그래밍 언어의 실행
- 프로그램은 텍스트를 컴파일러를 활용해 추상 문법 트리(AST)로 변환한다.
- 컴파일러는 AST를 바이트 코드라는 하위 수준으로 표현한다.
- 바이트 코드는 런타임이라는 다른 프로그램에 바이트코드를 입력해 평가하고 결과를 얻는다.
타입스크립트 코드의 실행
- AST를 만들어 결과 코드를 내놓기 전에 타입 확인.
- TSC 소스 ⇒ TSC AST
- typechecker가 AST 확인
- TSC AST ⇒ JS 소스
- JS 소스 ⇒ JS AST
- AST ⇒ BYTE CODE
- RUNTIME이 BYTE CODE 평가
⇒ JS의 컴파일러와 런타임은 엔진 이라는 하나의 프로그램으로 합쳐진다(V8, spider monkey(firefox), JSCore(사파리), 샤크라(엣지))등
- ts를 js로 변환해서 webpack으로 번들링 하는 과정에 대해 조금더 조사해보기
2. 타입 시스템
type checker가 프로그램에 타입을 할당하는데 사용하는 규칙 집합
타입 시스템은 두가지로 나뉜다.
- 명시적 타입 : 어떤 타입을 사용하는지 컴파일러에 알려줌
- 타입 추론: 자동으로 타입을 추론하는 시스템
TS는 두 가지 시스템 모두의 영향을 받았다.
- TSC는
annotation
을 이용하여 명시적으로 타입을 지정한다.value: type
형태로 사용된다. - annotation의 단어 뜻에 대해 조금더 조사해보자.
let a: number = 1
let b: string = "hello"
let c: boolean[] = [true, false]
let a = 1
let b = "hello"
let c = [true, false]
- 왠만한 경우에는 타입스크립트가 타입을 추론하도록 두는 것이 코드를 줄일 수 있는 방법이므로, 어노테이션은 주로 생략한다.
1) 타입 결정
-
동적 타입 바인딩(dynamic type binding)
- 자바스크립트가 프로그램을 실행해야만 특정 타입을 알 수 있음을 의미한다.
- JS는 프로그램을 실행하기 전에는 타입을 알 수 없다.
- 타입스크립트는 점진적 타입을 확인 하는 언어(gradually typed). 컴파일 타임에 프로그램의 모든 타입을 알고 있을 때 최상의 결과를 보여줄 수 있으나, 컴파일 시 모든 타입을 알아야 하는 것은 아니다.
- JS에서 TS로 점진적인 마이그레이션 상황에서는 점진적 타입 확인이 유용할 수 있으나, TS로 시작하는 경우에는 모든 타입을 컴파일 타임에 지정하는 것을 목표로 해야 한다.
-
타입 변환이 자동적인가 ?
- JS는 약타입 언어이다. 자바스크립트는 암묵적 변환 때문에 문제를 추적하기 어렵고, 이는 자바스크립트의 문제점이라고 비판 받아왔다. 개발자가 암묵적으로 의미하는 바를 이해해야 하므로, 이는 커다란 프로젝트에서 문제가 될 수 있기 때문에 명시적인 타입 선언이 필요하다.
-
타입 변환 시기
- Javascript는 주어진 상황에서 개발자가 무엇을 의도하는지에 맞춰 변환하려 최대한 노력할 뿐, 특정 상황에서 타입을 따지지 않는다.
- TSC는 컴파일 타임에 코드 타입을 확인하기 때문에 코드를 실행하지 않고 예제 코드에 문제가 있음을 알려준다.
-
에러 검출시기
- Javascript는 런타임에 예외를 던지거나 암묵적 형변환을 수행한다. 즉 프로그램을 실행해야만 어떤 문제가 있음을 확인할 수 있다.
- 타입스크립트는 컴파일 타임에 문법 에러와 타입 관련 에러를 모두 검출한다.
Chapter 3. 타입의 모든 것
타입 : 값과 이 값으로 할 수 있는 일의 집합
- 어떤 값이 T 타입이면, 이 값을 가지고 어떤 일을 할 수 있고 어떤 일을 할 수 없는지도 알 수 있다.
- type checker가 유효하지 않은 동작이 실행되는 일을 예방 한다.
- 컴파일과 컴파일러의 용어 사용에 대해 조금더 알아보자.
function squareOf(n) { return n * n }
function squareOf(n: number) { return n * n } <= 여기서 반환 값에 대한 추론이 이루어진다.
- 타입을 제한 하면 tsc가 함수 호출시 인수가 해당 타입에 호환이 되는지 판단한다. 이를 경계 개념으로 해석할 수도 있는데, TSC에 n의 상위 한정값이 number 라 알려주면, 인자는 모두 number의 동등 또는 하위 개념이어야 한다.
1) TSC type
-
Any : 타입들의 조상
- 타입은 값과 값으로 수행할 수 있는 작업의 집합이다. any는 모든 값의 집합으로 모든 것을 할 수 있기에 TSC를 사용하는 의미가 줄어들게 되므로 왠만하면 사용하지 않는 것을 권한다.
- 만약, Any를 사용하게 되는 경우 반드시 명시적으로 Any를 사용하고, 암묵적 Any를 사용하지 않아야한다.
- Any 사용시 타입 검사 자체를 안함. 또한 타입을 안적은 경우 Any type이 되기도 함.(암묵적 Any)
-
unknown: 타입을 미리 알 수 없는 어떤 값이 있을 경우 any대신 unkown을 사용하는게 낫다.
- unkown도 모든 값을 대표하나, unkown 타입을 검사해 정제 하기 전까지 tsc가 unknown 타입의 값을 사용할 수 없게 강제한다.
- tsc는 type을 unknown이라 추론하지 않는다 (개발자가 명시적으로 사용 ⇒ null 처럼)
- unknown 타입이 아닌 값과 unknown 타입의 값을 비교할 수 있다.
- unknown값은 특정 타입임을 증명하고 난 이후에 그 타입이 지원하는 동작을 수행 할 수 있다.
- boolean: boolean, true, false 3가지 타입을 가질 수 있다.
- 값을 타입으로 사용하는 기능을
타입 리터럴(오직 하나의 값을 나타내는 타입)
이라고 부른다.
타입 리터럴을 가짐으로써, 해당 타입이 변하지 않는 것을 확신할 수 있으므로 수많은 버그를 방지할 수 있다.
-
number
- tsc가 값이 number임을 추론하게 함
let a = 4472; let b = Infinity * 0.10
- const를 이용해 특정 number임을 추론하게 함
const c = 4723
- 값이 number임을 tsc에게 명시적으로 알림
let f: number = 10
- tsc에 값이 특정 number임을 명시적으로 알림
let z: 28 = 27.13
;
- tsc가 값이 number임을 추론하게 함
-
string
- number처럼 추론이 가능하다. 그러므로 tsc가 추론하도록 두는 것이 좋다.
-
Object
- 객체의 형태를 정의한다.
- 자바스크립트가 Ductyping(구조 기반 타입) 을 갖도록 설계 되어 있기 때문에 tsc도 js의 스타일을 선호한다.
- 객체 서술방식
- object 선언
let a: object = { b: "x", } a.b // 'b' 프로퍼티는 object에 존재하지 않음
- 객체 리터럴 문법. tsc가 c의 형태를 추론하게 하거나, 중괄호 안에 명시적으로 타입을 묘사
const a = { b: 'x' } a.b // string const b = { c: { d: 'f' } } // {c: {d: string}}
- 객체를 const로 선언해도 tsc는 더 좁은 타입으로 추론하지 않는데, 객체 자체를 const로 정의했을 때 객체의 변경은 불가능 하지만, 객체 내부의 값은 변경 가능하기 때문이다.
- 객체 리터럴 문법은 객체리터럴 또는 클래스 일수도 있다.
let c: { firstName: string lastName: string } = { firstName: 'kim', lastName: 'dowoo' } class Person { constructor ( public firstName: string, public lastName: string ) {} } # firstName c = new Person('adam', 'smith');
이처럼, 객체 리터럴로 타입을 표기한 경우 필요한 프로퍼티를 제공하지 않거나, 타입에 지정되지 않은 프로퍼티를 추가하려고 하면 에러가 나타난다.
- 타입 스크립트는 변수를 선언하고 나중에 초기화 하는 상황에서, tsc는 변수를 사용하기 전에 값을 할당하도록 강제한다.
let a; if (isTrue) { a = 123; } else { b = 4; } a.~~~ if (b.~~) { } else { a }
이처럼, tsc는 객체 프로퍼티에 엄격한 편이다. 만약 개발자가 이후에 객체에 프로퍼티를 추가하거나, 프로퍼티 추가가 선택적일 경우에는 다음과 같이 사용할 수도 있다.
let a: { b: number c?: string [key: number]: boolean }
인덱스 시그니처
는 객체가 여러 키를 가질 수 있음을 알려준다. 이 객체에서 모든 T타입의 key는 U 타입의 값을 갖는다라고 해석하며, 이 인덱스 시그니처를 활용하여 명시적으로 정의한 키 외에 다양한 키를 객체에 안전하게 추가할 수 있다.인덱스 시그니처
는 반드시number
나string
타입에 할당할 수 있는 타입이어야 한다(number는 index, string은 key type으로 활용되기 때문)
⇒ CSS in JS 에서 특히 자주 사용됨.
객체 타입을 정의할 때 선택형(?)뿐만 아니라
readonly
한정자를 이용해 특정 필드를 읽기 전용으로 정의할 수 있다.let user: { readonly firstName: string } = { firstName: 'Kim' }
- 빈 객체 타입 (
{}
) - null과 undefined를 제외한 모든 타입에 할당할 수 있으나 사용하기 까다롭게 만들므로 가능한 피하는 것이 좋다.
- Object 타입
- 이도 가능한 사용하지 않는게 좋다.
- Tuple
- 배열의 서브 타입이다.
- 길이가 고정되었고, 인덱스의 타입이 알려진 배열의 일종이다.
- 튜플은 선언시 타입을 명시해야 한다.
let a: [number] = [1] let b: [string, string, string] = ["kim", "dowoo", "FE"]
- 튜플은 선택형 요소도 지원한다.
let trainFares: [number, number?][] = [ [3.75], [8.12, 4.13], [5.13, 4.12] ]
- 튜플이 최소 길이를 갖도록 지정할 때는
...
를 사용할 수 있다.
let friends: [stirng, ...string[]] = ['Kim', 'Lee', 'Park', 'Jung']; let list: [number, boolean, ...string[]] = [1, false, 'a','b']
- 튜플은 이형(heterogeneous)배열을 안전하게 관리할 뿐 아니라 배열 타입의 길이도 조절하므로 순수 배열에 비해 안정성을 높일 수 있기 때문에 배열 사용을 권장한다.
- 일반 배열은 가변(
.push
,.splice
, 갱신 등의 작업)적인 반면, 상황에 따라 불변인 배열이 필요하다. TSC는readonly
배열 타입을 기본으로 지원하므로 이를 기반으로 불변 배열을 만들 수 있다. 이 readonly 배열을 바꾸기 위해서는concat
,slice
등을 사용하여 새 배열을 만드는 문법을 사용해야 한다.
let as: readonly number[] = [1,2,3]; let bs: readonly number[] = as.concat(4); as[4] = 5 // readonly 의 index signature 는 read만 허용
type A = readonly string[] // readonly string[]; type B = ReadonlyArray<string> type C = Readonly<string[]> type D = readonly [number, string] type E = Readonly<[number, string]>
- null, undefined, void, never
- undefined: undefined
- null: null
- void: 명시적으로 아무것도 반환하지 않는 함수의 반환 타입
- never: 절대 반환하지 않는 (예외를 던지나 영원히 실행)
- unknown은 모든 타입의 상위 타입이고, never는 모든 타입의 서브타입이다.
- Enum
- 열거형은 해당 타입으로 사용할 수 있는 값을 열거하는 기법이다.
- 키를 값에 할당하는, 순서가 없는 자료구조다.
- 키가 컴파일 타임에 고정된 객체이다.
- 문자열 ⇒ 문자열 mapping, 문자열 ⇒ 숫자 mapping이 있다.
enum Language { English, Spanish, Russian, } // 열거형의 이름은 단수 명사로 쓰고, 첫 문자는 대문자로 하는 것이 관례이다.
- tsc는 자동으로 열거형의 각 멤버에 적절한 숫자를 추론해 할당하지만, 값을 명시적으로 설정 할 수도 있다.
- 열거형은 객체처럼 열거형 값에 데이터를 가져올 수 있다.
Language.English
- 열거형은 값을 입력하지 않으면 배열의 인덱스처럼 값이 할당되나, 직접 입력을 할 수도 있다.
- 타입스크립트는 여러 열거형 정의 중 한가지 값만 추론할 수 있으므로, 열거형을 분할할 때 주의해야 하며, 열거형 멤버에 명시적으로 값을 할당하는 게 좋다.
- const enum 을 사용하면 더 안전하게 enum을 사용할 수 있다. 그러나 역방향 찾기를 지원하지 않으므로 일반 자바스크립트 객체와 비슷해진다.
- const enum은 자바스크립를 생성하지 않으며, 그 대신 필요한 곳에 열거형 멤버의 값을 채워 넣는다.
- 숫자 값을 받는 열거형은 전체 열거형의 안전성을 해칠 수 있다.
- 열거형을 안전하게 사용하는 방법은 까다로우므로 열거형 자체를 멀리 하는 것도 나쁘지 않다…