일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- ESlint
- dfs
- webpack
- async/await
- 리터럴 타입
- Cypress
- Promise
- 결정 알고리즘
- tailwind
- Jest
- 공변성
- TS
- 인터섹션
- React
- CORS
- map
- 무한 스크롤
- useAppDispatch
- RTK Query
- 호이스팅
- CI/CD
- app router
- 반공변성
- 이분 검색
- 투포인터
- SSR
- 태그된 유니온
- autosize
- recoil
- 타입 좁히기
Archives
- Today
- Total
짧은코딩
타입 설계의 중요성 본문
반응형
타입을 잘 설계하면 코드를 직관적으로 작성할 수 있다. 타입을 잘 설계하기 위해서는 유효한 상태만 표현할 수 있는 타입을 만들어야한다.
웹 애플리케이션 예시
문제점이 있는 코드
-페이지를 렌더링 해주는 함수
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 상태에서 페이지를 변경해도 문제가 없다.
유효한 상태만 표현하는 타입이 코드가 길어지는 단점이 있지만 길게 봤을 때 시간이 절약되고 고통이 줄어든다.
반응형
'TS > 이펙티브 타입스크립트' 카테고리의 다른 글
인터페이스의 유니온 사용하기 (1) | 2023.03.27 |
---|---|
"모두 null" || "모두 null이 아닌 데이터"로 모델링 하기 (0) | 2023.03.27 |
타입 추론과 문맥 (0) | 2023.03.13 |
비동기는 async 사용 (0) | 2023.03.09 |
객체 생성하기 (0) | 2023.03.07 |
Comments