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;
  • string

    • number처럼 추론이 가능하다. 그러므로 tsc가 추론하도록 두는 것이 좋다.
  • Object

    • 객체의 형태를 정의한다.
    • 자바스크립트가 Ductyping(구조 기반 타입) 을 갖도록 설계 되어 있기 때문에 tsc도 js의 스타일을 선호한다.
    • 객체 서술방식
    • object 선언
    let a: object = {
      b: "x",
    }
    a.b // 'b' 프로퍼티는 object에 존재하지 않음
    1. 객체 리터럴 문법. 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 타입의 값을 갖는다라고 해석하며, 이 인덱스 시그니처를 활용하여 명시적으로 정의한 키 외에 다양한 키를 객체에 안전하게 추가할 수 있다.
    • 인덱스 시그니처 는 반드시 numberstring 타입에 할당할 수 있는 타입이어야 한다(number는 index, string은 key type으로 활용되기 때문)

    ⇒ CSS in JS 에서 특히 자주 사용됨.

    객체 타입을 정의할 때 선택형(?)뿐만 아니라 readonly 한정자를 이용해 특정 필드를 읽기 전용으로 정의할 수 있다.

    let user: {
      readonly firstName: string
    } = {
      firstName: 'Kim'
    }
    1. 빈 객체 타입 ({})
    2. null과 undefined를 제외한 모든 타입에 할당할 수 있으나 사용하기 까다롭게 만들므로 가능한 피하는 것이 좋다.
    3. Object 타입
    4. 이도 가능한 사용하지 않는게 좋다.
    5. Tuple
    6. 배열의 서브 타입이다.
    7. 길이가 고정되었고, 인덱스의 타입이 알려진 배열의 일종이다.
    8. 튜플은 선언시 타입을 명시해야 한다.
    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은 자바스크립를 생성하지 않으며, 그 대신 필요한 곳에 열거형 멤버의 값을 채워 넣는다.
    • 숫자 값을 받는 열거형은 전체 열거형의 안전성을 해칠 수 있다.
    • 열거형을 안전하게 사용하는 방법은 까다로우므로 열거형 자체를 멀리 하는 것도 나쁘지 않다…

© 2024 Doe의 devlog, Built with Vapor blog Theme Gatsby