이번 편의 결과물: npm run verify가 타입 테스트를 포함해 전부 통과하고, API 클라이언트·폼 스키마·유틸 타입 라이브러리가 갖춰진 expense-tracker 최종본을 브라우저에서 시연합니다. · 다루는 개념: npm run verify 전체 실행, 기능 체크리스트, 다음 과목 예고
01편에서 점검했던 typescript_fundamentals 종료 상태의 expense-tracker에, 17편까지 조건부 타입부터 zod 통합 API 클라이언트까지 쌓아 올렸습니다. 이 편에서는 새 코드를 추가하지 않고, 지금까지 만든 것이 하나의 명령으로 전부 검증되는지 확인한 뒤 결과물을 시연합니다.
이 편에서 만드는 파일
expense-tracker/
└── package.json ~ (verify 스크립트에 test:types 단계 추가)개념 정리
verify에 타입 테스트 추가하기
typescript_fundamentals 18편에서 만든 verify 스크립트는 typecheck·test·build 세 단계였습니다. 16편에서 추가한 test:types(vitest --typecheck)는 지금까지 verify에 포함되지 않아, 유틸 타입이 깨져도 verify가 통과할 수 있는 빈틈이 있었습니다. 이 편에서 그 빈틈을 막습니다.
// package.json (발췌)
{
"scripts": {
"typecheck": "tsc --noEmit",
"test:types": "vitest --typecheck --run",
"test": "vitest run",
"build": "vite build",
"verify": "npm run typecheck && npm run test:types && npm run test && npm run build"
}
}typecheck(전체 프로젝트 타입 검사) 다음에 test:types(유틸 타입 회귀 검사)를 두어, 가장 가벼운 검사부터 순서대로 실패를 드러냅니다. test(런타임 동작 검사)와 build(번들 생성)는 그 뒤를 잇습니다.
실습
1. verify 스크립트 갱신
package.json을 위 내용대로 고칩니다.
2. 전체 검증 실행
npm run verify> tsc --noEmit
(오류 없음)
> vitest --typecheck --run
✓ src/lib/types/utility.test-d.ts (6)
✓ src/lib/types/brand.test-d.ts (5)
✓ src/lib/types/pipe.test-d.ts (2)
Type Test Files 3 passed (3)
> vitest run
✓ tests/createTransaction.test.ts (1)
✓ tests/storage.test.ts (2)
✓ tests/stats.test.ts (3)
✓ tests/apiClient.test.ts (4)
✓ tests/transactionForm.test.ts (3)
> vite build
✓ built in 431ms3. 기능 체크리스트로 브라우저 시연
npm run preview아래 항목을 브라우저에서 하나씩 확인합니다.
- 거래 추가 폼에 잘못된 금액(음수)이나 빈 메모를 입력하면,
TransactionFormSchema(14편)가 만든 에러 메시지가 필드 옆에 표시된다. - 거래를 추가·수정·삭제하면 네트워크 탭에 MSW가 가로챈
/api/transactions요청과 응답이 찍힌다(15편). - 응답 데이터의
id는TransactionId브랜디드 타입(07편)이라, 이 값을 카테고리 자리에 잘못 넣는 코드는 편집기에서 바로 오류로 표시된다. - 예산 규칙 설정 화면에서 기본값은
DeepReadonly(06편)로 고정되어 있고, 부분 수정 폼은DeepPartial(06편)로 일부 필드만 받는다. - 통계 패널의
groupBy결과 타입이UnwrapPromise·ElementOf(03편)로 정확히 좁혀져, 존재하지 않는 프로퍼티에 접근하면 컴파일 오류가 난다. - 개발자 도구 콘솔에서
apiClient.request('GET', '/transactions')와apiClient.request('POST', '/transactions', body)(09편 오버로드)를 각각 호출하면 반환 타입이 서로 다르게 추론된다.
4. lib/types 최종 트리와 유틸 요약
lib/types/에 실제로 남는 파일은 다음 네 개입니다. 각 파일의 완전한 코드는 이미 03·05·06·07·11·17편에서 작성했으므로 여기서는 다시 싣지 않습니다.
expense-tracker/src/lib/types/
├── index.ts (17, 배럴)
├── utility.ts (03, 05, 06, 17)
├── brand.ts (07, 17)
├── pipe.ts (11, 17)
├── utility.test-d.ts (16)
├── brand.test-d.ts (16)
└── pipe.test-d.ts (16)| 유틸 | 소속 파일 | 한 줄 설명 |
|---|---|---|
UnwrapPromise<T> | utility.ts | 중첩된 Promise를 재귀적으로 벗겨 내부 값 타입을 반환 |
ElementOf<T> | utility.ts | 배열·읽기 전용 배열 타입에서 요소 타입을 추출 |
FormErrors<T> | utility.ts | 각 필드 키에 Error 접미사를 붙인 선택적 에러 메시지 맵으로 리매핑 |
JsonValue | utility.ts | 문자열·숫자·불리언·null과 중첩 배열·객체를 포괄하는 JSON 재귀 유니온 |
DeepPartial<T> | utility.ts | 중첩 객체의 모든 단계를 선택적 프로퍼티로 전환 |
DeepReadonly<T> | utility.ts | 중첩 객체의 모든 단계를 readonly로 전환 |
Brand<T, B> | brand.ts | 원시 타입에 문자열 태그를 붙여 구조가 같아도 섞이지 않게 하는 브랜드 헬퍼 |
TransactionId | brand.ts | 거래 식별자 전용 브랜디드 문자열 타입 |
CategoryId | brand.ts | 카테고리 식별자 전용 브랜디드 문자열 타입 |
pipe(value, ...fns) | pipe.ts | 가변 튜플로 단계별 입출력 타입을 이어 맞추는 함수 합성 유틸 |
직접 해보기
package.json의verify순서를build가test:types보다 앞에 오도록 바꾼 뒤,lib/types/utility.ts의UnwrapPromise정의를 의도적으로 깨뜨려verify가 어디서 멈추는지 비교해 보세요.- 위 기능 체크리스트 중 하나를 골라, 어떤 편에서 그 동작이 만들어졌는지 목차를 보지 않고 코드만 보고 추적해 보세요.
정답 보기
순서를 바꾸면 UnwrapPromise가 깨진 상태에서도 build(번들링)는 타입을 검사하지 않으므로 먼저 성공하고, 그 다음 test:types 단계에서야 실패가 드러납니다. 실패를 가장 빠르게 알고 싶다면 가벼운 검사(typecheck, test:types)를 무거운 검사(build)보다 앞에 두는 편이 낫습니다.
자주 하는 실수
| 증상 | 원인 | 고치는 법 |
|---|---|---|
verify는 통과했는데 배포 후 유틸 타입 관련 버그 발견 | test:types 단계를 verify에 포함하지 않음 | verify 스크립트에 test:types를 반드시 넣는다 |
| MSW 네트워크 로그가 안 보임 | npm run preview는 프로덕션 번들을 서빙할 뿐, MSW 서비스 워커 등록 코드가 프로덕션 빌드에서 빠져 있음 | 브라우저 시연은 npm run dev(MSW 활성화)에서 하고, preview는 번들 결과 확인용으로 구분한다 |
| 체크리스트 항목이 어떤 편 코드인지 못 찾음 | 배럴·리팩터링(17편) 이후 파일 위치가 바뀜 | lib/types/index.ts에서 시작해 각 export의 선언부까지 따라간다 |