[1장] 타입스크립트 알아보기
이펙티브 타입스크립트 1장을 정리한 내용입니다.
작성일 2024.02.13
페이지가 생성된 시간 2024.10.01 21:01:08

6

이펙티브 타입스크립트 1장

아이템1 TS와 JS의 관계

타입스크립트는 자바스크립트의 상위집합이다.

모든 자바스크립트는 타입스크립트이다.
모든 타입스크립트가 자바스크립트는 아니다.

타입스크립트는 런타임 오류를 발생시키는 코드 또는 의도대로 동작하지 않는 코드를 미리 찾아준다.

  • 존재하지 않는 메서드 호출, undefined 관련 런타임 오류 (런타임오류 O)
  • 잘못된 프로퍼티값 참조 (런타임 오류 X, 의도와는 다른)

하지만 타입스크립트의 타입과 실제 값에 차이가 있는 경우도 있다.

/** 타입체크는 통과하지만 런타임 오류를 발생시키는 코드 */
const names: string[] = ['Alice', 'Bob'];
console.log(names[2].toUpperCase());

/** 타입체크를 통과하지 못하는 코드 */
const names: [string, string] = ['Alice', 'Bob'];
console.log(names[2].toUpperCase());

아이템2 타입스크립트 설정

타입스크립트 컴파일러 옵션 설정하기

  • 커멘드라인
    • tsc --noImplicitAny program.ts
  • tsconfig.json 파일
    • { "compilerOptions": {} }

noImpicitAny 설정은 필수

타입스크립트는 타입 정보를 가질 때 가장 효과적이다


strictNullChecks 설정

코드 작성을 어렵게 한다.

이유는 필수적으로 null, undefined를 체크해야 하기 때문이다. (조건문이 늘거나 타입단언이 추가됨)

하지만 null, undefined 관련 오류를 잡아 내는 데 많은 도움이 된다.

strictNullChecks 설정 없이 개발한다면
"undefined는 객체가 아닙니다" 라는 런타임 오류를 주의하자

엄격한 타입 체크를 원한다면 strict 설정을 하자

/** strict 설정 시 추가되는 세부속성 (출처: chat GPT) */
{
  strictNullChecks: true,
  strictFunctionTypes: true,
  strictBindCallApply: true,
  strictPropertyInitialization: true,
  noImplicitThis: true,
  alwaysStrict: true,
}

아이템3 코드생성과 타입은 무관

코드생성

TS 파일을 구버전의 JS 파일로 트랜스파일 하는 것!

타입체크

말그대로 코드의 타입 오류 체크

위 두 가지가 완전히 독립적이다.


두 역할의 무관이 끼치는 영향

1. 타입 오류가 있는 코드도 컴파일이 가능하다

컴파일 언어의 관점에서 생각해보면 말도 안된다

개발자 편의를 제공(오류난 부분 외 다른 부분 테스트 가능)


2. 런타임에는 타입이 없다는걸 명심해야 한다

내가 작성하는 코드가 런타임에도 동작할까?

/** 런타임에서도 타입 정보를 유지하는 방법 */
interface Square {
  kind: 'square';
  width: number;
}

if (shape.kind === 'square') {
  // shape Square 타입으로 보정 (런타임에도 유효)
}

/**
 * 이렇게 하면 타입 체커도 알아듣고 런타임에서도 동작하고~
 * 또는 애초부터 class를 사용하는 방법도 있다
 * class는 타입, 값으로 모두 사용가능
 */

3. 타입 연산은 런타임에 영향을 주지 않는다.

타입 단언문(type assertion)은 타입 연산이다.

따라서 JS로 변환된 코드에서는 유효하지 않고, 당연히 런타임에 영향이 없다.

타입 단언문으로 타입을 정제하는 것은 결국 런타임에 아무런 영향을 미치지 못한다는 소리!

결국에 값을 정제하기 위해서는 런타임에서의 타입을 체크해야 하고, 타입스크립트 타입 체커는 이것을 통해 타입 정제를 해준다.


4. 런타임 타입은 선언된 타입과 다를 수 있다.

API를 잘못 파악하거나 추후 API가 변경되는 경우 런타임 타입과 선언된 타입이 다를 수 있다는 것을 명심하자.


5. 타입으로는 함수 오버로딩이 불가능

C++ 과 같은 언어는 동일한 이름의 매개변수만 다른 여러 함수 작성을 허용하고 이 것을 함수 오버로딩이라고 함.

타입 수준에서의 함수 오버로딩을 지원하지만, 컴파일된 결과물은 함수 하나라는 사실을 기억하자


6. 타입은 런타임 성능과는 무관

타입은 컴파일시 제거되므로 런타임 성능과는 아무 관련이 없음.

그냥 당연한 거임.


아이템4 구조적 타이핑에 익숙해지기

호환되는 타입(?)

interface Vector2D {
  x: number;
  y: number;
}

interface Vector3D {
  x: number;
  y: number;
  z: number;
}

function calcVector2DLength(vector: Vector2D) {
  const { x, y } = vector;
  return Math.sqrt(x * x + y * y);
}

const vector3D: Vector3D = { x: 1, y: 2, z: 3 };

/**
 * 아래 코드는 에러가 발생하지 않음.
 * Vector3D의 구조가 Vector2D의 구조와 '호환'되기 때문
 * calcVector2DLength 함수가 느끼기에 구조적으로 동일하다
 * 이를 '구조적 타이핑'이라고 부른다
 */
calcVector2DLength(vector3D);

열려(?)있는 타입스크립트 타입

interface Vector {
  x: number;
  y: number;
  z: number;
}

const vector: Vector = { x: 1, y: 2, z: 3 };
for (const axis in vector) {
  console.log(vector[axis]);
  /**
   * 'string' 형식의 식을
   * '{ x: number; y: number; z: number; }' 인덱스 형식에
   * 사용할 수 없으므로 요소에 암시적으로 'any' 형식이 있습니다.
   *
   * '{ x: number; y: number; z: number; }' 형식에서
   * 'string' 형식의 매개 변수가 포함된 인덱스 시그니처를 찾을 수 없습니다.
   */
}

위 예시 외에도 클래스 관련 할당문에서 당황스러운 결과가 있다.

class C {
  constructor(public foo: string) {}
}

const d: C = { foo: 'foo' };
/** d는 C클래스 인스턴스와 '구조적으로 동일하므로 오류가 아니다' */

테스트 작성에서의 유리함

구조적으로 모킹하기는 쉬우므로 유닛 테스트 작성하기가 쉽다.

또한 실제 DB나 API에 붙이기 쉽다.


아이템5 any 타입 쓰지말자

any를 마구마구 쓰면?

  • 개발자 경험이 좋지 않아진다.

    • 남발한 any만큼 타입스크립트 언어 서비스를 누릴 수 없다
    • 따라서 팀의 생산성이 저하된다. (any는 나만 불편한게 아니라 팀원들도 불편해지는 '똥'이다)
  • 리펙토링이 힘들어진다.

    • 코드의 구현을 변경하면 그에 따라 다른 변경점들을 알려주는데 any쓰면 그게 안됨.
  • 나 편하자고 남발한 any는 팀원은 어떤 타입인지 알기 힘들다.

  • any가 있으면 런타임 오류가 발생할 확률이 늘어나고 개발자의 불안감을 높인다. -> 코드의 신뢰도가 떨어진다


1장 요약

  • noImplicitAny, strictNullChecks는 설정해주자
  • 타입 체크와 런타임은 독립적이다. 런타임에 영향을 주는 코드가 뭔지 잘 판단하자
  • 타입스크립트는 구조적 타이핑이라는 것을 알아두자
  • any 쓰지 말자. (noImplicitAny 같이 쓰자)
©2024 dlwl98
github
PostsAbout