Part2_Type
김은정
책 읽기

2장. 타입

타입이란

  • 자료형으로서의 타입

    • 7가지 데이터 타입(자료형) : undefined, null, Boolean, String, Symbol, Numeric, Object)
    • 메모리에 값을 저장 시, 타입을 사용해 값의 종류를 명시해 메모리를 효율적으로 사용 가능
  • 집합으로서의 타입

    • 값이 가질 수 있는 유효한 범위의 집합
    • 코드에서 사용되는 유효한 값의 범위를 제한 → 런타임에 발생할 수 있는 유효하지 않은 값에 대한 에러 방지
  • 정적 타입과 동적 타입

    • 정적 타입 : 모든 변수의 타입이 컴파일 타임에 결정 → 컴파일 타임에 타입 에러를 발견할 수 있음 → 안정성 보장

    • 동적 타입 : 변수 타임이 런타임에서 결정 → 언제 프로그램에 오류가 발생할 지 불안함

    • 타입스크립트는 정적 타입이다.

    컴파일 타임 : 기계(컴퓨터, 엔진)가 소스코드를 이해할 수 있도록 기계어로 변환되는 시점

    런타임 : 변환된 파일이 메모리에 적재되어 실행되는 시점

  • 강타입과 약타입

    • 암묵적 타입 변환 여부에 따라 타입 시스템을 강타입과 약타입으로 분류

    암묵적 타입 변환 : 개발자가 의도적으로 타입을 명시하거나 바꾸지 않았는데도 컴파일러 또는 엔진 등에 의해 런타임에 타입이 자동으로 변경되는 것

    • 강타입 : 파이썬, 루비, 타입스크립트

    • 약타입 : C++, 자바, 자바스크립트

    • 서로 다른 타입을 갖는 값끼리 연산을 시도하면

      → 강타입 : 에러 발생

      → 약타입 : 내부적으로 판단해 특정 값의 타입을 변환해 연산을 수행한 후 값을 도출 (암묵적 변환)

    • 암묵적 변환 → 편리하지만 예기치 못한 오류가 발생할 가능성도 높아짐

    ⇒ 타입을 명시하거나 타입스크립트가 타입을 추론하도록 해, 유효하지 않은 작업 수행을 방지해야 함.

  • 컴파일 방식

    • 타입스크립트가 탄생한 이유 : 자바스크립트의 컴파일 타임에 런타임 에러를 사전에 잡아내기 위한 것
    • 타입스크립트를 컴파일하면 타입이 모두 제거된 자바스크립트 소스코드만이 남게 됨

타입스크립트의 타입 시스템

  • 타입 애너테이션 방식

    타입 애너테이션(type annotation) : 변수나 상수 혹은 함수의 인자와 반환 값에 타입을 명시적으로 선언해서 어떤 타입 값이 저장될 것인지를 컴파일러에 직접 알려주는 문법

    • 변수 이름 뒤에 : type 구문을 붙여 데이터 타입을 명시해줌
  • 구조적 타이핑

    • 타입스크립트는 구조로 타입을 구분한다. 이것을 구조적 타이핑이라고 한다.
  • 구조적 서브 타이핑

    구조적 서브 타이핑 : 객체가 가지고 있는 속성(프로퍼티)을 바탕으로 타입을 구분하는 것

    • 이름이 다른 객체라도 가진 속성이 동일하다면 타입스크립트는 서로 호환이 가능한 동일한 타입으로 여김
    // 예시 1. 가진 속성이 동일한 두 객체
    interface Pet {
      name: string
    }
     
    interface Cat {
      name: string
      age: number
    }
     
    let pet: Pet;
    let cat: Cat = { name: "Zag", age: 2 };
     
    // ✅ OK
    pet = cat;
     
    // 예시 2. 함수의 매개변수에도 적용
    interface Pet {
      name: string
    }
    let cat = { name: "Zag", age: 2 };
    function greet(pet: Pet) {
      console.log(`Hello, ${pet.name}`);
    }
     
    greet(cat); // ✅ OK
    • 타입의 상속 역시 구조적 타이핑 기반이다
    class Person {
      name: string;
     
      age: number;
     
      constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
      }
    }
     
    class Developer {
      name: string;
     
      age: number;
     
      sleepTime: number;
     
      constructor(name: string, age: number, sleepTime: number) {
        this.name = name;
        this.age = age;
        this.sleepTime = sleepTime;
      }
    }
     
    function greet(p: Person) {
      console.log(`Hello, I'm ${p.name}`);
    }
     
    const developer = new Developer("zig", 20, 7);
     
    greet(developer); // Hello, I'm zig
    • 서로 다른 두 타입 간의 호환성은 오로지 타입 내부의 구조에 의해 결정된다.
    • 타입 A가 타입 B의 서브 타입이라면 A 타입의 인스턴스는 B 타입이 필요한 곳에 언제든지 위치할 수 있다.
    • 즉, 타입이 계층 구조로부터 자유롭다.
  • 자바스크립트를 닮은 타입스크립트

    • 타입스크립트의 타입 시스템은 구조적 타이핑을 사용

    • 이는 ‘명목적 타이핑’과는 대조적인 방식

    명목적 타이핑 : 타입의 구조가 아닌 타입의 이름만을 가지고 구별하는 것, 같은 이름의 데이터 타입으로 선언된 경우에만 서로 호환됨

    • 명목적 타이핑은 타입의 동일성을 확인하는 과정에서 구조적 타이핑에 비해 조금 . 더안전하다.

    • 그럼에도 타입스크립트가 구조적 타이핑을 채택한 이유는 타입스크립트가 자바스크립트를 모델링한 언어이기 때문

    • 자바스크립트는 본질적으로 덕 타이핑(duck typing)을 기반으로 한다.

    덕 타이핑 : 어떤 타입에 부합하는 변수와 메서드를 가질 경우 해당 타입에 속하는 것으로 간주하는 방식.

    ‘만약 어떤 새가 오리처럼 걷고, 헤엄치며 꽥꽥거리는 소리를 낸다면 나는 그 새를 오리라고 부를 것이다’

    • 구조적 타이핑 덕분에 타입스크립트에서는 객체 간 속성이 동일하다면 서로 호환되는 구조적 타입 시스템을 제공하여 편리성을 높임
    • 자바스크립트의 덕 타이핑과 타입스크립트의 구조적 타이핑은 서로 구분되는 타이핑 방식이지만, 실제 사용하는 코드를 보면 별 차이가 없어 보인다.
    • 두 가지 타이핑 방식 모두 객체가 가진 속성을 기반으로 타입을 검사하기 때문
    • 덕 타이핑과 구조적 타이핑 모두 객체 변수, 메서드 같은 필드를 기반으로 타입을 검사한다는 점에서 동일하지만, 타입을 검사하는 시점이 다르다.
      • 덕 타이핑은 런타임에 타입을 검사한다. ⇒ 동적 타이핑에서 사용
      • 구조적 타이핑은 컴파일 타임에 타입 체커가 타입을 검사한다. ⇒ 정적 타이핑에서 사용
  • 구조적 타이핑의 결과

    • 타입스크립트 구조적 타이핑의 특징 때문에 예기치 못한 결과가 나올 때도 있다.
    interface Cube {
      width: number
      height: number
      depth: number
    }
     
    function addLines(c: Cube) {
      let total = 0;
     
      for (const axis of Object.keys(c)) {
        // 🚨 Element implicitly has an 'any' type
        // because expression of type 'string' can't be used to index type 'Cube'.
        // 🚨 No index signature with a parameter of type 'string'
        // was found on type 'Cube'
        const length = c[axis];
     
        total += length;
      }
    }
     
    // 아래와 같은 상황(c[axis]의 타입이 string 일 수 있음) 때문에 에러 발생
    const namedCube = {
      width: 6,
      height: 5,
      depth: 4,
      name: "SweetCube", // string 타입의 추가 속성이 정의되었다
    };
     
    addLines(namedCube); // ✅ OK
    • 타입스크립트 구조적 타이핑의 특징으로 Cube 타입 값이 들어갈 곳에 name 같은 추가 속성을 가진 객체도 할당할 수 있기 때문에 발생하는 문제

    ⇒ 이러한 한계를 극복하고자 타입스크립트에 명목적 타이핑 언어의 특징을 가미한 식별할 수 있는 유니온 같은 방법이 생겨났다.

  • 타입스크립트의 점진적 타입 확인

    점진적 타입 검사 : 컴파일 타임에 타입을 검사하면서 필요에 따라 타입 선언 생략을 허용하는 방식

    • 타입 선언을 생략하면 암시적 타입 변환이 일어남

    • 타입을 명시하지 않았을 때 타입스크립트 컴파일러는 함수의 인자와 반환 값을 모두 any 타입으로 추론

    any 타입 : 타입스크립트 내 모든 타입의 종류를 포함하는 가장 상위 타입으로 어떤 값이든 할당 가능

  • 자바스크립트 슈퍼셋으로서의 타입스크립트

  • 값 vs 타입

    값 : 프로그램이 처리하기 위해 메모리에 저장하는 모든 데이터

    • 타입을 명시하는 방법으로는 :type, type 키워드, interface 키워드 등이 있음
    • 값 공간과 타입 공간의 이름은 서로 충돌하지 않음 ( ← type으로 선언한 내용은 런타임에서 제거되기 때문)
    • 타입은 주로 타입선언(:) 또는 단언문(as)로 작성하고 값은 할당 연산자인 =으로 작성
    • 타입과 값이 혼용되는 것 말고도 타입 공간에 동시에 존재하는 심볼도 있음. 대표적인 것이 클래스enum
    • 클래스 - 타입 애너테이션으로 사용할 수 있지만 런타임에서 객체로 변환되어 자바스크립트의 값으로 사용되는 특징을 가짐
    class Developer {
      name: string;
     
      domain: string;
     
      constructor(name: string, domain: string) {
        this.name = name;
        this.domain = domain;
      }
    }
     
    const me: Developer = new Developer("zig", "frontend");
    • enum - 타입 공간에서 타입을 제한하는 역할을 하지만 자바스크립트 런타임에서 실제 값으로도 사용될 수 있음
    enum WeekDays {
      MON = "Mon",
      TUES = "Tues",
      WEDNES = "Wednes",
      THURS = "Thurs",
      FRI = "Fri",
    }
    // ‘MON’ | ‘TUES’ | ‘WEDNES’ | ‘THURS’ | ‘FRI’
    type WeekDaysKey = keyof typeof WeekDays;
     
    function printDay(key: WeekDaysKey, message: string) {
      const day = WeekDays[key];
      if (day <= WeekDays.WEDNES) {
        console.log(`It’s still ${day}day, ${message}`);
      }
    }
     
    printDay("TUES", "wanna go home");
    • 자바스크립트 문법의 여러 키워드가 타입스크립트에서 값 또는 타입으로 해석되는 방식
    키워드타입
    classYY
    const, let, varYN
    enumYY
    functionYN
    interfaceNY
    typeNY
    namespaceYN

enum과 유니온


  1. 배달이팀 의견
    • 제한은 없지만 enum을 선호하는 편

    • 유니온 타입 → 내가 어떤 타입을 가졌는지 전부 기억해야 하고, 변경이 필요하면 사용되는 곳을 모두 찾아서 변경해야 함 ⇒ 리팩터링하기 번거로움

    • enum → 정의부를 변경하면 자동으로 변경됨 ⇒ 넓은 범위에 확장해서 사용할 경우 유용, 코드 가독성 좋음

    • 물론, enum은 트리쉐이킹이 되지 않기 때문에 번들 사이즈에 영향을 줄 수 있지만 const enum을 사용하여 해결할 수 있음

    트리쉐이킹 : 코드 어디에서도 사용하지 않는 코드(dead code, 이른바 죽은 코드)를 삭제해서 최종 번들 크기를 줄이는 과정

    • 사실 enum을 쓴다고 해서 전체 파일의 번들 사이즈가 서비스에 영향을 미칠정도로 커지지는 않음
  2. 냥이팀 의견
    • enum을 잘 사용하지 않음
    • enum은 타입을 위한 문법이라기보다 개발을 위한 문법 같음
    • enum의 기능이 타입스크립트 컴파일러에 의해서 동작하는 것이 이상하게 느껴짐
  3. 메이팀 의견
    • enum을 지양하고 있음

    • 사용하기에는 편하지만 성능에 영향을 줄 수 있다는 걸 본 후로 습관적으로 안 쓰기 시작 enum 외에 const enum을 사용하나요?


  1. 배달이팀
    • enumaration(열거) 폴더를 따로 만들어서 사용하고 있음
    • 이 폴더에 정의한 enum을 외부에서 전역적으로 참조할 때는 const enum을 사용함
    • const enum은 빌드 과정에서 참조 값만 남기기 때문에 트리쉐이킹이 된다는 장점이 있음
    • 어차피 enum도 상수를 쓰기 위한 것으로 생각하기 때문에 const enum을 사용하는 것이 적절하다고 판단됨
    • 물론 isolate 모드를 활성화하고 const enum을 쓰면 안 된다는 의견도 있음
    • 현재로서는 const enum이 더 명확하고 정적인 값을 사용할 수 있다는 장점이 더 큰 것 같음
  2. 왕팀
    • const enum을 사용하지 않음

    • const enum은 enum과 다르게 직접적인 값으로 치환되기 때문에 전체 네임스페이스에 접근하지 못하고 순회할 수 없다는 단점을 가지고 있는 것 같음 트리쉐이킹

  • 자바스크립트, 타입스크립트에서 사용하지 않는 코드를 삭제하는 방식
  • 웹팩, 롤업 같은 모듈 번들러를 사용해 번들링 작업을 수행할 때 사용하지 않는 코드는 자동으로 삭제
  • 트리쉐이킹을 통해 더 작은 크기의 번들링 파일을 생성할 수 있음
  • 타입을 확인하는 방법

    • typeof, instanceof, 타입 단언을 사용해서 확인 가능
    • typeof : 연산하기 전에 피연산자의 데이터 타입을 나타내는 문자열 반환
    class Developer {
      name: string;
     
      sleepingTime: number;
     
      constructor(name: string, sleepingTime: number) {
        this.name = name;
        this.sleepingTime = sleepingTime;
      }
    }
     
    const d = typeof Developer; // 값이 ‘function’
     
    type T = typeof Developer; // 타입이 typeof Developer
    // type T에 할당된 Developer는 인스턴스의 타입이 아니라 new 키워드를 사용할 때 볼 수있는 생성자 함수
     
    const zig: Developer = new Developer("zig", 7);
    type ZigType = typeof zig; // 타입이 Developer
    // zig 인스턴스는 Developer가 인스턴스 타입으로 생성됨
    • typeof 연산자처럼 instanceof 연산자의 필터링으로 타입이 보장된 상태에서 안전하게 값의 타입을 정제하여 사용할 수 있음
    let error: unknown;
     
    if (error instanceof Error) {
      showAlertModal(error.message);
    } else {
      throw Error(error);
    }
    • 타입 단언으로 타입을 강제할 수 있는데 이때 as 키워드를 사용하면 됨
    • 타입 단언은 강제 형 변환과 유사한 기능을 제공
    const loaded_text: unknown; // 어딘가에서 unknown 타입 값을 전달받았다고 가정
     
    const validateInputText = (text: string) => {
      if (text.length < 10) return "최소 10글자 이상 입력해야 합니다.";
      return "정상 입력된 값입니다.";
    };
     
    validateInputText(loaded_text as string); // as 키워드를 사용해서 string으로 강제하지 않으면 타입스크립트 컴파일러 단계에서 에러 발생
    • 타입을 검사하는 방법으로 ‘타입 가드’라는 패턴도 있음
    • 타입 가드 : 특정 조건을 검사해서 타입을 정제하고 타입 안정성을 높이는 패턴
  • 원시 타입

    • 자바스크립트의 변수 → 어떤 타입의 값이라도 자유롭게 할당 가능
    • 타입스크립트 → 이 변수에 타입을 지정할 수 있는 타입 시스템 체계를 구축
    • 원시 타입 종류 : boolean, undefined, null, number, bigInt, string, symbol
  • 객체 타입

    • 객체 타입 종류 : object, , array, type과 interface 키워드, function

    • object

      • 가급적 사용하지 말도록 권장
      • any 타입과 유사하게 객체에 해당하는 모든 타입 값을 유동적으로 할당할 수 있어 정적 타이핑의 의미가 크게 퇴색되기 때문
    • (중괄호)

      • 객체를 타이핑할 때 사용하는데, 중괄호 안에 객체의 속성 타입을 정해주는 식으로 사용
      • const obj:{} = {}; 와 같이 사용도 가능. 그러나 이 경우엔 어떠한 값도 속성으로 할당 불가능
    • array

      • 타입스크립트 배열 타입은 하나의 타입 값만 가질 수 있다는 점에서 자바스크립트 배열보다 조금 더 엄격
      • 타입스크립트에서 배열을 선언하는 방식은 Array 키워드 또는 대괄호([])를 사용
      • 주의할 것은 ‘튜플’ 타입도 대괄호로 선언한다!
      • 튜플은 배열과 유사하지만 튜플의 대괄호 내부에는 선언 시점에 지정해준 타입 값만 할당할 수 있음.
      • 원소 개수도 타입 선언 시점에 미리 정해줌
      • 이는 객체 리터럴에서 선언하지 않은 속성을 할당하거나, 선언한 속성을 할당하지 않을 때 에러가 발생한다는 점에서 비슷
      const targetCodes: ["CATEGORY", "EXHIBITION"] = ["CATEGORY", "EXHIBITION"]; // (O)
      const targetCodes: ["CATEGORY", "EXHIBITION"] = [
        "CATEGORY",
        "EXHIBITION",
        "SALE",
      ]; // (X) SALE은 지정할 수 없음
    • type과 interface 키워드

      • 중괄호를 사용한 객체 리터럴 방식으로 타입을 매번 일일이 지정하기에는 중복적인 요소가 많음

      • type 또는 interface 키워드를 사용해 선언하면 반복적으로 사용돼도 중복 없이 해당 타입 사용 가능

    type과 interface를 둘 다 쓸 수 있는 상황에서 팀은 주로 어떤 것을 사용하나요?


    1. 배달이팀

      • 대부분의 상황에서 interface 사용, 간단한 용도로는 type도 사용
      • 공식 문서에 쓰인 내용을 바탕으로 전역적으로 사용할 때는 interface를, 작은 범위 내에서 한정적으로 사용한다면 type
    2. 냥이팀

      • type은 어떤 값에 대한 정의같이 정적으로 결정되어 있는 것을,
      • interface는 확장될 수 있는 basis를 정의하거나 어떤 object 구성을 설명하는 요소
    3. 봉다리팀

      • 선언 병합이 필요할 때는 interface
      • computed value를 사용해야 한다면 type
    4. 왕팀

    • interface의 필요성을 느끼지 못하고 있어 type 정의를 주로 사용

    팀 내에서 type이나 interface만을 써야 하는 상황이 있었나요?


    1. 배달이팀
      • interface : 객체 지향적으로 코드를 짤 때, 특히 상속하는 경우에 (ex: extends, implements 사용 시)
    2. 냥이팀
      • type : 유니온 타입이나 교차 타입 등 type 정의에서만 쓸 수 있는 기능을 활용할 때
      • interface : 기존 인터페이스를 정의하고 확장할 때
    3. 메이팀
      • type : props에 Record 형식을 extends할 때 interface로 선언된 변수를 넣으면 에러가 발생해서 type으로 변경함. interface처럼 인덱스 키가 따로 설정되지 않으면 오류가 발생하는 듯
    4. 봉다리팀
      • type : computed value를 써야 했을 때
    • function

      • 함수 자체의 타입을 지정하는 방식은 ‘호출 시그니처’를 정의하는 방식

      호출 시그니처 : 함수의 매개변수와 반환 값의 타입을 명시하는 역할을 함

      type add = (a: number, b: number) => number;
      • 타입스크립트에서 함수 자체의 타입을 명시할 때는 화살표 함수 방식으로만 호출 시그니처를 정의