[타입추론 19] 장황한 코드 방지

2022. 7. 22. 00:44책/이펙티브 타입스크립트

TS는 생각보다 훨씬 타입을 잘 추론한다. 따라서 많은 타입 구문은 불필요하며 비생산적이다. 

 

1. 타입 선언에 대하여

  • 타입스크립트가 자체적으로 타입을 추론할 수 있다면 타입 구문을 작성하지 않는게 좋다
  • 이상적인 타입스크립트 코드는 함수/메서드 시그니처에 타입 구문을 포함하지만, 함수 내에서 생성된 지역 변수에는 타입 구문을 넣지 않는다.
// 'x', 'y'를 보고 string 타입을 생각했겠지만
// 더 정확히 말하면 리터럴 타입에 가깝다
// 그리고 TS가 이를 훨씬 더 잘 추론해낸다
const axis1 :string = 'x';
const axis2 = 'y';

interface Product {
  id :string;
  name :string;
  price :number;
}

// 매개변수의 타입이 지정돼있다면
// 비구조화 할당문 만으로도 
// TS는 지역 변수의 타입들을 전부 추론해낼 수 있다
function logProduct(product :Product) {
  const {id, name, price} = product; // 타입선언 불필요

  console.log(id, name, price);
}

 

2. 명시적 타입 구문이 필요한 경우
내부 구현의 오류

 

2.1 객체 리터럴 정의

  • 타입 명시를 해두면 잉여 속성 체크 덕분에 오타나 프로퍼티 작성 오류를 잡는 데 효과적이다
  • 변수가 사용되는 순간이 아닌 할당하는 시점에 오류가 표시된다
    (만약 잉여 속성 체크가 없었다면 코딩 다 끝나고 런타임 돌리는 시점에 오류가 났을 것임)
interface Product {
  id :string;
  name :string;
  price :number;
}

할당하는 시점에 오류가 표시된다

님 잉여 속성 체크에 걸리셨는데요??
이거 이대로 두고 계속 코드 짤거임?? 실화?
const elem :Product = {
  name: 'ABC',
  id: '1023i4',
  
}


2.2 함수의 반환값

  • TS가 자체적으로 함수의 반환값을 추론할 수 있는 경우도 있다
  • 그러나 반환 타입을 명시하면 아래 이점들이 생긴다
    1) 오류의 위치를 제대로 표시해준다 (애초에 잘못 짠 함수인데 나중에 쓸 때 가서 오류나면 빡칠걸?)
    2) 반환 타입을 명시하면 함수 자체를 더욱 명확하게 알 수 있다. 즉, 함수 시그니처를 잘 작성해둠으로써 주먹구구식으로 시그니처가 작성되는 것을 방지한다 (TDD와 비슷한 느낌)
    3) 명명된 타입을 사용할 수 있다
  • 참고로 eslint 규칙 중 no-inferrable-types를 걸어두면 타입 구문이 진짜로 필요한 것인지 확인할 수 있다
// return number[] 라고 잘 추론한다
function cal(elem :number[]) {
  return elem.map(v => v*v);
}

1) 오류 표출
아래 getQuote 함수는 number | Promise<any>를 반환한다

const cache: {[ticker :string] :number} = {};
function getQuote(ticker :string) {
  // 얘는 number를 반환한다
  if (ticker in cache) {
    return cache[ticker];
  }

  // 얘는 Promise를 반환한다
  return fetch(`https://example.com/?q=${ticker}`)
    .then(response => response.json())
    .then(quote => {
      cache[ticker] = quote;
      return quote;
    });
}

만약 캐시된 값이 반환된다면 타입이 promise가 아니므로 then을 쓸 수 없다.
이에 따라 함수를 호출한 코드에서 오류가 발생한다

getQuote('MSFT').then(console)


애초에 코드 자체를 잘못 짰다고 말하는게 더 나을 텐데 굳이?
function getQuote(ticker :string) :Promise<number>{
  if (ticker in cache) {
    return cache[ticker]; // number형식은 프로미스가 아니다 라고 오류남
  }

  ...
}

3) 명명된 타입
2D 벡터를 입력받아 합성하는 함수
아마 반환 타입이 2D 벡터일 것라고 예상했을 것이다

그러나 TS는 { x: number; y :number; }라고 추론한다
아니 Vector2D로 추론해달라니까 머함;;

interface Vector2D { x :number; y :number; }
function add(a :Vector2D, b :Vector2D) {
  return {
    x: a.x + b.x,
    y: a.y + b.y,
  };
}