반응형
250x250
Notice
Recent Posts
Recent Comments
Link
관리 메뉴

짧은코딩

타입 설계의 중요성 본문

TS/이펙티브 타입스크립트

타입 설계의 중요성

5_hyun 2023. 3. 22. 21:06

타입을 잘 설계하면 코드를 직관적으로 작성할 수 있다. 타입을 잘 설계하기 위해서는 유효한 상태만 표현할 수 있는 타입을 만들어야한다.

웹 애플리케이션 예시

문제점이 있는 코드

-페이지를 렌더링 해주는 함수

interface State {
  pageText: string
  isLoading: boolean
  error?: string
}
declare let currentPage: string

function renderPage(state: State) {
  if (state.error) {
    return `Error! Unable to load ${currentPage}: ${state.error}`
  } else if (state.isLoading) {
    return `Loading ${currentPage}...`
  }
  return `<h1>${currentPage}</h1>\n${state.pageText}`
}

이 코드는 isLoading이 true고 error이 존재하면 어떤 상태인지 명확하게 구분할 수 없다. 필요한 정보가 부족하다.

 

-페이지를 전환하는 함수

async function changePage(state: State, newPage: string) {
  state.isLoading = true
  try {
    const response = await fetch(getUrlForPage(newPage))
    if (!response.ok) {
      throw new Error(`Unable to load ${newPage}: ${response.statusText}`)
    }
    const text = await response.text()
    state.isLoading = false
    state.pageText = text
  } catch (e) {
    state.error = '' + e
  }
}

이 함수에는 여러 문제점이 있다.

 

1. 오류가 발생하면 state.isLoading을 false로 만들어줘야 하는데 그렇지 않다.

2. state.error을 초기화 하지 않아 과거 오류 메시지를 보여줄 수 있다.

3. 페이지 로딩중에 사용자가 페이지를 바꾸면 새 페이지에 오류가 뜨거나, 이전 요청이 실행되어 전 페이지가 뜰 수 있다.

=> 종합해보면 상태 값의 정보가 부족하거나, isLoading이나 error가 모두 존재하여 속성들이 충돌이 날 수 있다.

개선한 코드

-타입 설계

interface RequestPending {
  state: 'pending'
}
interface RequestError {
  state: 'error'
  error: string
}
interface RequestSuccess {
  state: 'ok'
  pageText: string
}
type RequestState = RequestPending | RequestError | RequestSuccess

interface State {
  currentPage: string
  requests: { [page: string]: RequestState }
}

태그된 유니온을 사용하여 각 상태를 명시적으로 모델링하고 있다. 코드가 길어졌지만 무효한 상태를 허용하지 않는다.

 

-렌더링 함수와 페이지 전환 함수

function renderPage(state: State) {
  const { currentPage } = state
  const requestState = state.requests[currentPage]
  switch (requestState.state) {
    case 'pending':
      return `Loading ${currentPage}...`
    case 'error':
      return `Error! Unable to load ${currentPage}: ${requestState.error}`
    case 'ok':
      return `<h1>${currentPage}</h1>\n${requestState.pageText}`
  }
}

async function changePage(state: State, newPage: string) {
  state.requests[newPage] = { state: 'pending' }
  state.currentPage = newPage
  try {
    const response = await fetch(getUrlForPage(newPage))
    if (!response.ok) {
      throw new Error(`Unable to load ${newPage}: ${response.statusText}`)
    }
    const pageText = await response.text()
    state.requests[newPage] = { state: 'ok', pageText }
  } catch (e) {
    state.requests[newPage] = { state: 'error', error: '' + e }
  }
}

타입 설계를 유효하게 했기 때문에 코드르 더 쉽게 구현할 수 있었고 모호함은 사라졌다. 현재 페이지가 무엇인지 명확하며 pending 상태에서 페이지를 변경해도 문제가 없다.

 

유효한 상태만 표현하는 타입이 코드가 길어지는 단점이 있지만 길게 봤을 때 시간이 절약되고 고통이 줄어든다.

728x90
반응형
Comments