Item 28 유효한 상태만 표현하는 타입을 지향하기
Before
interface State {
pageText: string
isLoading: boolean
error?: 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
값이 존재하면 로딩 중인 상태인지 오류가 발생한 상태인지 명확히 구분할 수 없다. 필요한 정보가 부족하기 때문이다.
한편 페이지를 전환하는 changePage
함수는 다음과 같다.
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
}
}
changePage
함수에는 다양한 문제점이 있다.
오류가 발생했을 때
state.isLoading
을false
로 설정하는 로직이 빠져 있다.state.error
를 초기화하지 않았으므로 페이지 전환 중에 로딩 메시지 대신 과거의 오류 메시지를 보여 주게 된다.페이지 로딩 중에 사용자가 페이지를 바꿔 버리면 어떤 일이 벌어질지 예상하기 어렵다.
새 페이지에 오류가 뜨거나, 응답이 오는 순서에 따라 두 번째 페이지가 아닌 첫 번째 페이지로 전환될 수도 있다.
문제는 바로 상태 값의 두 가지 속성이 동시에 정보가 부족하거나(요청이 실패한 것인지 여전히 로딩 중인지 알 수 없다), 두 가지 속성이 충돌(오류이면서 동시에 로딩 중일 수 있다)할 수 있다는 것이다.
State
타입은isLoading
이true
이면서 동시에error
값이 설정되는 무효한 상태를 허용한다.무효한 상태가 존재하면
render()
,changePage()
둘 다 제대로 구현할 수 없게 된다.
After
애플리케이션의 상태를 제대로 표현해보자.
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 }
}
}
Summary
유효한 상태와 무효한 상태를 둘 다 표현하는 타입은 혼란을 초래하기 쉽고 오류를 유발한다.
유효한 상태만 표현하는 타입을 지향해야 한다. 코드가 길어지거나 표현하기 어렵지만 결국은 시간을 절약하고 고통을 줄일 수 있다.
Last updated
Was this helpful?