221022 TIL | 이펙티브 타입스크립트_#3 타입 공간과 값

아이템7~아이템 8까지 정리

타입스크립트의 타입 공간, 값 공간

  • 타입스크립트의 심벌은 타입 공간이나 값 공간 중 한 곳에 존재한다.
  • 이름이 같더라도 속하는 공간에 따라 다른 것을 나타낼 수 있다.
  • 예제의 interface Cylinder와 const Cylinder는 아무런 관련이 없다. 상황에 따라서 타입으로 쓰일 수도 값으로 쓰일 수도 있다.
    • 하지만 이런 점이 가끔 오류를 발생시킨다.
interface Cylinder {
  radius: number;
  height: number;
}

const Cylinder = (radius: number, height: number) => ({radius, height});

타입 공간, 값 공간이 같아 오류가 발생하는 케이스

interface Cylinder {
  radius: number;
  height: number;
}

const Cylinder = (radius: number, height: number) => ({radius, height});

function calculateVolume(shape: unknown) {
  if (shape instanceof Cylinder) {
    shape.radius
       // ~~~~~~ Property 'radius' does not exist on type '{}'
  }
}
  • instanceof는 1. 런타임 연산자이고 2. 값에 대해 연산을 한다
  • 때문에 instanceof Cylinder는 타입이 아닌 함수를 참조한다.
    • 타입 코드명과 값 코드명이 같을 경우 혼란스러워지는 이유이다.

타입 공간 값 공간에 대한 개념

  • 타입스크립트 코드를 읽을 때 타입인지 값인지 구분하는 방법을 터득하는 것이 중요하다.
type T1 = 'string literal';
type T2 = 123;
const v1 = 'string literal';
const v2 = 123;
  • type, interface 다음에 나오는 심벌은 타입인 반면 const, let 선언에 쓰이는 것은 이다.
  • 타입스크립트 플레이그라운드에서 타입스크립트로 구문을 작성하면 변환된 자바스크립트 코드를 보여준다.

    JavaScript With Syntax For Types.

    • 예제의 자바스크립트 코드
      const v1 = 'string literal';
      const v2 = 123;
    

자바스크립트 코드처럼 모든 값은 타입을 가지지만, 타입은 값을 가지지 않는다. type, interface같은 키워드는 타입 공간에만 존재한다는 것을 기억하자.

타입, 값 번갈아서 선언하기

  • 타입 선언(:) 또는 단언문(as) 다음에 나오는 심벌은 타입이지만, = 다음에 오는 모든 것은 값이다.
interface Person {
  first: string;
  last: string;
}
const p: Person = { first: 'Jane', last: 'Jacobs' };
//    -           --------------------------------- Values
//       ------ Type
function email(p: Person, subject: string, body: string): Response {
  //     ----- -          -------          ----  Values
  //              ------           ------        ------   -------- Types
  // COMPRESS
  return new Response();
  // END
}

class & enum

  • 상황에 따라 타입과 값 두 가지 모두 사용한 예약어이다.
class Cylinder {
  radius=1;
  height=1;
}

function calculateVolume(shape: unknown) {
  if (shape instanceof Cylinder) {
    shape  // OK, type is Cylinder
    shape.radius  // OK, type is number
  }
}
  • 클래스가 타입으로 쓰일 때 : 형태(속성, 메서드) 사용
  • 클래스가 값으로 쓰일 때 : 생성자 사용

typeof 연산자

  • typeof 연산자는 자바스크립트에도 존재하지만, 타입스크립트 타입이나 인터페이스 문법에 확장하여 사용할 수 있다.
  • typeof는 값을 읽어서 타입스크립트 타입을 반환한다.
  • 타입 공간 : typeof는 보다 큰 타입의 일부분으로 사용하거나 type 구문으로 이름을 붙이는 용도로도 사용할 수 있다.
  • 값 공간 : typeof는 자바스크립트 런타임 타입을 가리키는 문자열을 반환한다.
    • string, number, boolean, undefined, object, function
interface Person {
  first: string;
  last: string;
}
const p: Person = { first: 'Jane', last: 'Jacobs' };
//    -           --------------------------------- Values
//       ------ Type
function email(p: Person, subject: string, body: string): Response {
  //     ----- -          -------          ----  Values
  //              ------           ------        ------   -------- Types
  // COMPRESS
  return new Response();
  // END
}

class Cylinder {
  radius=1;
  height=1;
}

function calculateVolume(shape: unknown) {
  if (shape instanceof Cylinder) {
    shape  // OK, type is Cylinder
    shape.radius  // OK, type is number
  }
}
type T1 = typeof p;  // Type is Person
type T2 = typeof email;
    // Type is (p: Person, subject: string, body: string) => Response

const v1 = typeof p;  // Value is "object"
const v2 = typeof email;  // Value is "function"
const v = typeof Cylinder;  // 클래스가 실제 함수로 구현되기 때문에 값이 function
type T = typeof Cylinder;  // 타입이 typeof Cylinder
  • Cylinder 클래스는 생성자 함수이다.
declare let fn: T;
const c = new fn();  // 타입이 Cylinder
  • InstanceType을 사용해 생성자 타입을 인스턴스 타입으로 전환하는 것도 가능하다.
type C = InstanceType<typeof Cylinder>;  // 타입이 Cylinder

두 공간 사이에서 다른 의미를 가지는 코드 패턴

  • 값으로 쓰이는 this는 자바스크립트의 this 키워드이다.
    • 타입으로 쓰이는 this는 타입스크립트의 타입(또는 다형성 this 라고 불린다.)
    • 서브클래스의 메서드 체인을 구현할 때 유용하다.
  • 값에서 &와 는 AND와 OR 비트 연산자이다.
    • 타입에서는 인터섹션과 유니온이다.
  • const는 새 변수를 선언하지만, as const는 리터럴 또는 리터럴 표현식의 추론된 타입을 바꾼다.
  • extends는 서브클래스(class A extends B) 또는 서브타입(interface A extends B)또는 제너릭 타입의 한정자(Generic)를 정의할 수 있다.
  • in은 루프 또는 매핑된 타입에 등장한다.
  • 예시로 든 this, typeof 외에도 다른 많은 연산자들과 키워드들은 타입 공간과 값 공간에서 다른 목적으로 사용될 수 있다.

만약 타입스크립트 코드가 잘 작동하지 않는다면 타입 공간과 값 공간을 혼동해서 잘못 작성했을 가능성이 크다.

  • 함수를 구조 분해 할당을 이용하도록 변경한 코드이다.
  • 자바스크립트에서는 정상적으로 작동하지만, 타입스크립트에서는 오류가 발생한다.

자바스크립트일 경우 )

interface Person {
  first: string;
  last: string;
}
// 이렇게 단일 객체 매개변수를 받도록 작성한 코드를
function email(options: {person: Person, subject: string, body: string}) {
  // ...
}

// 구조분해할당으로 이렇게 변경할 수 있다.
function email({person, subject, body}) {
  // ...
}

타입스크립트일 경우 )

interface Person {
  first: string;
  last: string;
}
function email({
  person: Person,
       // ~~~~~~ 바인딩 요소 'Person'에 암시적으로 'any' 타입 형식이 있습니다.
  subject: string,
        // ~~~~~~ 'string' 식별자 중복
        //        'string'에 암시적으로 'any' 형식이 있음.
  body: string}
     // ~~~~~~ 'string' 식별자 중복
     //        'string'에 암시적으로 'any' 형식이 있음.
) { /* ... */ }
  • 값 관점에서 Person과 string이 해석되어, 오류가 발생한 것이다.
  • 타입스크립트는 Person과 string이라는 변수를 생성하려고 했다.
  • 문제를 해결하기 위해서는 타입과 값을 구분하는 작업이 필요하다.
function email(
  {person, subject, body}: {person: Person, subject: string, body: string}
) {
  // ...
}

출처

  • 이펙티브 타입스크립트