자료 유형: 무료 실무 해설팩
TypeScript 문법은 조금 알지만 API, 상태, optional 값에서 자주 막히는 학습자를 위한 실무 해설팩입니다. 타입 문법 암기가 아니라 실제 화면 버그를 줄이는 타입 설계 기준을 제공합니다.
이 팩의 약속
이 자료는 타입 이름을 외우게 하지 않는다. 실제 프론트엔드에서 자주 생기는 런타임 버그를 TypeScript로 어떻게 줄이는지 설명한다.
- API 상태를 여러 boolean으로 나눠 모순 상태를 만드는 문제
- 외부 JSON에
as User를 바로 붙이는 문제 - optional 값을 기본값 없이 렌더링하는 문제
- 제네릭 응답 타입을 만들지 않아 API마다 타입이 흩어지는 문제
예제 1. API 상태 모델링
연결 문제: https://nst21c.com/?task=tsDiscriminatedUnion#labApp
흔한 실패 코드
type UserListState = {
loading: boolean;
error: string | null;
users: User[] | null;
};
이 구조는 loading: true, error: "실패", users: [...]가 동시에 가능하다. 화면에서는 로딩 스피너, 에러 메시지, 목록이 동시에 보이는 식으로 깨질 수 있다.
개선 코드
type UserListState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; users: User[] }
| { status: "empty" }
| { status: "error"; message: string };
function getUserListMessage(state: UserListState) {
switch (state.status) {
case "loading":
return "사용자 목록을 불러오는 중입니다.";
case "success":
return `${state.users.length}명의 사용자를 찾았습니다.`;
case "empty":
return "조건에 맞는 사용자가 없습니다.";
case "error":
return state.message;
default:
return "검색 조건을 입력하세요.";
}
}
실무 화면 예
- 검색 결과
- 관리자 목록
- 상품 목록
- 대시보드 요약 카드
코드 리뷰 체크포인트
- 상태 이름 하나로 화면 분기가 가능한가?
- 성공 상태가 아닐 때
users에 접근하지 못하게 되어 있는가? - 빈 목록과 에러를 구분하는가?
예제 2. unknown JSON 검증
연결 문제: https://nst21c.com/?task=tsUnknownError#labApp
흔한 실패 코드
const user = (await response.json()) as User;
return user.name.toUpperCase();
서버가 { nickname: "Kim" }을 보내도 TypeScript는 막지 못한다. as User는 런타임 검증이 아니라 컴파일러에게 믿으라고 말하는 것이다.
개선 코드
type User = {
id: number;
name: string;
};
function isUser(value: unknown): value is User {
if (typeof value !== "object" || value === null) return false;
const candidate = value as Record<string, unknown>;
return typeof candidate.id === "number" && typeof candidate.name === "string";
}
async function loadUser(response: Response) {
const data: unknown = await response.json();
if (!isUser(data)) {
throw new Error("사용자 응답 형식이 올바르지 않습니다.");
}
return data;
}
실무 화면 예
- 외부 API 응답
- localStorage 복원
- URL query 파싱
- CSV/엑셀 import
코드 리뷰 체크포인트
- 외부 데이터는
unknown으로 받은 뒤 좁히는가? - 필수 필드의 존재와 타입을 확인하는가?
- 실패했을 때 호출부가 처리할 수 있는 에러를 던지는가?
예제 3. optional 필드와 기본값
연결 문제: https://nst21c.com/?task=tsOptional#labApp
흔한 실패 코드
type Profile = {
id: number;
name: string;
nickname?: string;
};
function getDisplayName(profile: Profile) {
return profile.nickname.toUpperCase();
}
nickname은 없을 수 있다. 그런데 바로 메서드를 호출하면 런타임 오류가 난다.
개선 코드
function getDisplayName(profile: Profile) {
return (profile.nickname ?? profile.name).toUpperCase();
}
실무 화면 예
- 닉네임 없는 사용자 프로필
- 썸네일 없는 상품 카드
- 소개글 없는 작성자 카드
- 선택하지 않은 필터 값
코드 리뷰 체크포인트
- optional 필드에 접근하기 전에 기본값을 정했는가?
||와??의 차이를 알고 쓰는가?- 화면에 빈 값이 보일 때 사용자에게 자연스러운 대체 문구가 있는가?
예제 4. 제네릭 API 응답 타입
연결 문제: https://nst21c.com/?task=tsApiResponse#labApp
흔한 실패 코드
type UserResponse = {
data: User;
error: string | null;
};
type ProductResponse = {
data: Product;
error: string | null;
};
API마다 같은 구조를 복사하면 나중에 meta, status, message 같은 공통 필드가 바뀔 때 여러 타입을 다 고쳐야 한다.
개선 코드
type ApiResponse<T> =
| { ok: true; data: T }
| { ok: false; error: string };
type UserResponse = ApiResponse<User>;
type ProductResponse = ApiResponse<Product>;
function getUserName(response: UserResponse) {
if (!response.ok) return "사용자를 불러오지 못했습니다.";
return response.data.name;
}
실무 화면 예
- 목록 API
- 상세 API
- 검색 API
- 저장/수정 API
코드 리뷰 체크포인트
- 반복되는 응답 구조를 제네릭으로 묶었는가?
- 성공/실패 응답을 타입으로 구분하는가?
- 실패 상태에서
data를 읽을 수 없게 되어 있는가?
5일 복습 루틴
1일차: API 상태 모델링 문제를 풀고 boolean 상태를 union으로 바꾼다. 2일차: unknown JSON 문제를 풀고 검증 함수를 작성한다. 3일차: optional 필드 문제를 풀고 기본값 기준을 정한다. 4일차: ApiResponse 제네릭 문제를 풀고 성공/실패를 분리한다. 5일차: 네 예제를 하나의 사용자 목록 화면 타입으로 합친다.
이 팩 다음에 보낼 후속 안내
TypeScript 팩을 받은 사용자는 React에서 이 타입을 실제 UI 상태와 연결하는 연습이 필요하다. 후속 메일에서는 React form validation, loading/error/retry 화면으로 연결한다.