Typescript

[Typescript] 이펙티브 타입스크립트 3 아이템19-20

sian han 2023. 3. 9. 09:12

아이템19 추론 가능한 타입을 사용해 장황한 코드 방지하기

타입 추론이 된다면 명시적 타입 구문은 필요하지 않다.

 

function square(nums: number[]) {
  return nums.map(x => x * x)
}
const squares = square([1, 2, 3, 4]) // Type is number[]

 

타입스크립트는 예상한 것 보다 더 정확하게 추론하기도 한다

const axis1: string = 'x' // Type is string
const axis2 = 'y' // Type is "y"

타입스크립트가 추론한 y 가 더 정확한 타입이다. 

 

 

아래는 기록을 위한 함수 logProduct 이다.

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

function logProduct(product: Product) {
  const id: number = product.id
  const name: string = product.name
  const price: number = product.price
  console.log(id, name, price)
}

여기서 각 변수에 명시된 타입들은 불필요하다. 

타입스크립트에서 추론되었기 때문에 굳이 명시해줄 필요가 없다.

 

근데 만약 id 가 number 에서 string 으로 타입이 변경되었다고 가정해보자.

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

function logProduct(product: Product) {
  const id: number = product.id
  // ~~ Type 'string' is not assignable to type 'number'
  const name: string = product.name
  const price: number = product.price
  console.log(id, name, price)
}

logProduct 함수 내의 명시적 타입 구문이 없는 경우에 코드는 타입 체커를 통과하지만, 명시했으므로 오류를 발생시킨다.

이때 logProduct 는 비구조화 할당문을 사용해 구현하는 것이 더 좋다.

 

비구조화 할당 = 구조분해할당

 - 구조분해활당은 모든 지역 변수의 타입이 추론되도록 한다.

 - 따라서 여기에 명시적으로 타입 구문을 넣는것은 불필요하며 코드가 번잡해진다

function logProduct(product: Product) {
  const { id, name, price } = product
  console.log(id, name, price)
}

 

객체리터럴을 정의할 때 타입이 추론될 수 있지만 타입을 명시하고 싶을 수 있다

const elmo: Product = {
  name: 'Tickle Me Elmo',
  id: '048188 627152',
  price: 28.99,
}

객체리터럴에 타입을 정의했기 때문에 잉여속성체크가 동작한다. 만약 타입구문을 제거한다면 객체를 선언한 곳이 아니라 객체가 사용되는 곳에서 타입 오류가 발생한다.

 

 

주식시세를 조회하는 함수 getQuote 가 있다.

const cache: { [ticker: string]: number } = {}
function getQuote(ticker: string) {
  if (ticker in cache) { //이미 조회한 종목은 다시 요청하지 않도록 캐시를 추가했다
    return cache[ticker]
  }
  return fetch(`https://quotes.example.com/?q=${ticker}`)
    .then(response => response.json())
    .then(quote => {
      cache[ticker] = quote
      return quote
    })
}

이때 if 문 내의 return 타입과 밖의 return 문은 타입이 다르다. if 구문에는 동일하게 Promise 가 반환되어야 한다.

따라서 if 구문에는 cache[ticker] 가 아니라 Promise.resolve(cache[ticker]) 가 반환되는 것이 옳다. 근데 ! 함수에서 오류를 발생시키고 있지 않다. 왜냐하면 타입을 명시해주지 않았기 때문에 ~

 

함수 반환타입을 명시해주니까 오류를 발생시키고 있다. 

 

요약

  • 타입스크립트가 타입을 추론할 수 있다면 타입 구문을 작성하지 않는게 좋다
  • 이상적인 경우 함수/메서드의 시그니처에는 타입구문이 있지만, 함수 내의 지역변수에는 타입 구문이 없다
  • 추론될 수 있는 경우라도 객체 리터럴과 함수 반환에는 타입 명시를 고려해야한다. 이는 내부 구현의 오류가 사용자 코드 위치에 나타나는 것을 방지해준다

아이템20 다른 타입에는 다른 변수 사용하기

 

function fetchProduct(id: string) {}
function fetchProductBySerialNumber(id: number) {}
let id = '12-34-56' 
fetchProduct(id)

id = 123456
// ~~ '123456' is not assignable to type 'string'.
fetchProductBySerialNumber(id)
// ~~ Argument of type 'string' is not assignable to
//    parameter of type 'number'

타입스크립트는 '12-34-56' 값을 보고 id 타입을 string 으로 추론했다. string 타입에는 number 를 할당할 수 없기 때문에 오류가 발생했고, 여기서 변수의 값은 바뀔 수 있지만 타입은 바뀌지 않는다는 것을 알 수 있다.

 

id 를 string 과 number 를 모두 포함할 수 있도록 유니온 타입으로 확장해보자

let id: string | number = '12-34-56'
fetchProduct(id)

id = 123456 // OK
fetchProductBySerialNumber(id) // OK

 

그렇지만 별도의 변수를 도입하는 것이 낫다. id 를 사용할 때마다 값이 어떤 타입인지 확인해야하기 때문이다. 

 

아래와 같이 다른 타입에는 별수의 변수를 사용하는 것이 바람직하다.

  • 이렇게 하면 let 대신 const 로 변수를 선언할 수 있게 되는데, const 로 변수를 선언하면 코드가 간결해지고, 타입 체커가 타입을 추론하기에도 좋다. 타입이 바뀌는 변수는 되도록이면 피하자 ! 
const id = '12-34-56'
fetchProduct(id)

const serial = 123456 // OK
fetchProductBySerialNumber(serial) // OK

 

요약

  • 변수의 값은 바뀔 수 있지만 타입은 일반적으로 바뀌지 않는다.
  • 혼란을 막기 위해 타입이 다른 값을 다룰 때에는 변수를 재사용하지 않도록 한다.