221020 TIL | 이펙티브 타입스크립트_#1 타입스크립트 알아보기

~아이템5까지 정리

타입스크립트와 자바스크립트의 관계

  • 타입스크립트는 자바스크립트의 상위 집합이다.
  • 모든 자바스크립트 프로그램은 이미 타입스크립트 프로그램이기도 하다.
  • 하지만 타입스크립트는 별도의 문법을 가지고 있기 때문에 유효한 자바스크립트 문법이 아니다.

타입스크립트의 역할

  • 타입스크립트는 자바스크립트 런타임 동작을 모델링하는 타입 시스템을 가지고 있어 런타임에 오류를 발생시킬 코드를 미리 찾아낸다.

→ 타입스크립트가 정적 시스템이라는 것은 이런 특징 때문이다.

“모든” 오류를 찾아내진 않는다. 타입 체커를 통과하고도 오류가 발생할 수 있다.

const states = [
  {name: 'Alabama', capitol: 'Montgomery'},
  {name: 'Alaska',  capitol: 'Juneau'},
  {name: 'Arizona', capitol: 'Phoenix'},
  // ...
];
for (const state of states) {
  console.log(state.capital);
                 // ~~~~~~~ Property 'capital' does not exist on type
                 //         '{ name: string; capitol: string; }'.
                 //         Did you mean 'capitol'?
}

타입 선언을 하지 않으면 타입스크립트는 capitol이 맞는 표현인지, capital이 맞는 표현인지 모른다.

interface State {
  name: string;
  capital: string;
}
const states: State[] = [
  {name: 'Alabama', capitol: 'Montgomery'},
                 // ~~~~~~~~~~~~~~~~~~~~~
  {name: 'Alaska',  capitol: 'Juneau'},
                 // ~~~~~~~~~~~~~~~~~
  {name: 'Arizona', capitol: 'Phoenix'},
                 // ~~~~~~~~~~~~~~~~~~ Object literal may only specify known
                 //         properties, but 'capitol' does not exist in type
                 //         'State'.  Did you mean to write 'capital'?
  // ...
];
for (const state of states) {
  console.log(state.capital);
}

타입 구문을 추가하면 오류를 바로 찾아낼 수 있다.

타입스크립트 설정

  1. 커맨드라인 방법 : $ tsc —noImplicitAny program.ts
  2. 파일 : tsconfig.json 설정 권장방식
    • 설정 파일은 tsc —init으로 생성 가능

자바스크립트 프로젝트를 타입스크립트로 전환하는 게 아니라면 noImplicitAnystricNullCheck를 체크하는 것이 좋다.

  • noImplicitAny : 미리 정의된 타입이 없다면 any 할당할 지 여부
  • stricNullCheck : null과 undefined가 모든 타입에서 허용되는지 여부

strict 설정을 하면 엄격한 문법이 적용되며 둘 다 적용할 수 있다.

interface Square {
  width: number;
}
interface Rectangle extends Square {
  height: number;
}
type Shape = Square | Rectangle;

function calculateArea(shape: Shape) {
  if (shape instanceof Rectangle) {
                    // ~~~~~~~~~ 'Rectangle' only refers to a type,
                    //           but is being used as a value here
    return shape.width * shape.height;
                    //         ~~~~~~ Property 'height' does not exist
                    //                on type 'Shape'
  } else {
    return shape.width * shape.width;
  }
}

instanceof는 런타임에 일어난다.

타입스크립트 파일은 자바스크립트로 컴파일되는 과정에서 모든 인터페이스, 타입, 구문 모두 제거하기 때문에 런타임까지 타입 정보 유지되지 않음.

타임 시점 타입을 체크하고 싶다면

  1. heigh 속성이 존재하는지 체크해본다.

     interface Square {
       width: number;
     }
     interface Rectangle extends Square {
       height: number;
     }
     type Shape = Square | Rectangle;
     function calculateArea(shape: Shape) {
       if ('height' in shape) {
         shape;  // Type is Rectangle
         return shape.width * shape.height;
       } else {
         shape;  // Type is Square
         return shape.width * shape.width;
       }
     }
    
  2. 런타임에 접근 가능한 정보를 명시적으로 저장하는 태그 기법

     interface Square {
       kind: 'square';
       width: number;
     }
     interface Rectangle {
       kind: 'rectangle';
       height: number;
       width: number;
     }
     type Shape = Square | Rectangle;
        
     function calculateArea(shape: Shape) {
       if (shape.kind === 'rectangle') {
         shape;  // Type is Rectangle
         return shape.width * shape.height;
       } else {
         shape;  // Type is Square
         return shape.width * shape.width;
       }
     }
    

    여기서 Shape 타입은 ‘태그된 유니온(tagged union)’의 예시이며 타입스크립트에서 쉽게 볼 수 있다.

    타입(런타임 접근 불가), 값(런타임 접근 가능)을 둘다 사용하는 기법도 있다.

  3. Square와 Rectangle을 클래스로 만드는 방법

class Square {
  constructor(public width: number) {}
}
class Rectangle extends Square {
  constructor(public width: number, public height: number) {
    super(width);
  }
}
type Shape = Square | Rectangle;

function calculateArea(shape: Shape) {
  if (shape instanceof Rectangle) {
    shape;  // Type is Rectangle
    return shape.width * shape.height;
  } else {
    shape;  // Type is Square
    return shape.width * shape.width;  // OK
  }
}

타입과 타입 연산자는 자바스크립트 런타임 변환 시점 제거되기 때문에 런타임의 성능에 영향을 주지 않는다.

자바스크립트는 덕타이핑 타입스크립트는 구조적 타이핑

  • 자바스크립트는 duck typing 기반이다.

    Untitled

duck typing이란? 어떤 타입에 부합하는 변수와 메서드를 가질 경우, 객체를 해당 타입에 속하는 것으로 간주하는 방법이다. ”어떤 새가 오리처럼 걷고 오리처럼 헤엄치고 꽥꽥거리는 소리가 난다면 오리라고 부를 것이다!” 라는 명제로 정의되는 duck test 에서 유래된 말이다.

타입스크립트는 이를 모델링하기 위해 구조적 타이핑을 사용한다.
명시적 선언이나 이름을 기반으로 하는 명목적 타입 시스템(Nominal Type System)인 Java, C#과는 다르다.

interface Vector2D {
  x: number
  y: number
}
interface NamedVector {
  name: string
  x: number
  y: number
}
function calculateLength(v: Vector2D) {
  return Math.sqrt(v.x * v.x + v.y * v.y)
}
const v: NamedVector = { x: 3, y: 4, name: 'mgh' }
calculateLength(v)
  • 다음과 같은 예제가 있을 때 구조적 타이핑 언어에서는 Vector2D의 속성에 해당하는 값이 값을 넣는 타입에 속성으로 존재하는가를 판단한다.
    • 집합 개념으로 생각하면 이해하기 편할 것이다.

any 타입을 지양하기

  • any 타입은 안정성이 없다.
    • age += 1;
    • age에 ‘12’라는 string 타입 값이 있다면 런타임 정상, age는 “121”이 출력된다.
  • any 타입은 언어 서비스가 적용되지 않는다.
    • 심벌에 타입이 있다면 타입스크립트는 자동완성 기능, 도움말을 제공한다. any는 지원을 받지 못 한다.
    • 리팩토링을 예로 들어 VSCode에서 Rename Symbol을 이용해 심볼명을 변경할 수 있는데, any 타입은 변경되지 않는다.
  • any 타입은 타입 체커와 타입스크립트 언어 서비스를 무력화하고 시스템의 신뢰도를 떨어뜨린다. 최대한 사용을 피하는 것이 좋다.

출처

  • 이펙티브 타입스크립트