프론트엔드 실무 해설팩 01 통합 샘플

자료 유형: 무료 실무 해설팩

이 통합 샘플은 TypeScript API 상태, unknown JSON 검증, React 폼 흐름처럼 프론트엔드 실무에서 자주 막히는 대표 문제를 한 번에 비교해 볼 수 있도록 구성했습니다.

이 자료의 약속

이 자료는 정답 코드만 보여주지 않는다. 각 예제는 다음 순서로 설명한다.

  1. 왜 틀렸는지
  2. 어떤 실무 상황에서 같은 문제가 생기는지
  3. 어떻게 고쳐야 하는지
  4. 코드 리뷰에서 무엇을 확인해야 하는지
  5. 사이트에서 다시 풀어볼 문제

1. TypeScript API 상태 모델링

연결 문제: https://nst21c.com/?task=tsDiscriminatedUnion#labApp

흔한 실패 코드

type State = {
  loading: boolean;
  error: string | null;
  data: User[] | null;
};

이 구조는 처음에는 쉬워 보이지만 실제 화면에서는 모순 상태가 생긴다. 예를 들어 loading: true이면서 data도 있고 error도 있는 상태를 타입이 막아주지 못한다.

왜 틀렸는지

여러 boolean 필드를 조합해서 화면 상태를 표현하면 불가능해야 하는 상태가 코드상으로 가능해진다. 이 문제는 API 화면에서 특히 자주 발생한다. 로딩 중인지, 성공했는지, 실패했는지, 데이터가 비어 있는지를 렌더링 단계에서 계속 추측하게 된다.

개선 코드

type ApiState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; message: string };

function getMessage(state: ApiState<User[]>) {
  if (state.status === "loading") return "불러오는 중입니다.";
  if (state.status === "error") return state.message;
  if (state.status === "success") return `${state.data.length}명`;
  return "아직 요청하지 않았습니다.";
}

실무에서 어떻게 쓰는지

상품 목록, 사용자 상세, 검색 결과, 대시보드 카드처럼 API 결과를 보여주는 화면에 쓴다. 특히 React 컴포넌트에서 state.status로 분기하면 화면 상태가 읽기 쉬워지고, 코드 리뷰에서도 빠진 상태를 찾기 쉽다.

코드 리뷰 체크포인트

  • loading, success, error가 동시에 true가 될 수 없는 구조인가?
  • 성공 상태에서만 data에 접근하도록 타입이 막아주는가?
  • 실패 상태에는 사용자가 이해할 수 있는 메시지 또는 재시도 액션이 있는가?

2. unknown JSON 검증

연결 문제: https://nst21c.com/?task=tsUnknownError#labApp

흔한 실패 코드

async function loadUser() {
  const res = await fetch("/api/user");
  const json = await res.json();
  return json as User;
}

as User는 TypeScript에게만 “User라고 믿어”라고 말하는 것이다. 실제 서버 응답이 깨져 있거나 필드명이 바뀌어도 런타임에서는 아무 검증이 일어나지 않는다.

왜 틀렸는지

외부 API, localStorage, URL query, 폼 입력값은 모두 런타임 데이터다. TypeScript 타입은 컴파일 단계에서만 동작하므로 서버가 보낸 JSON이 정말 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() {
  const res = await fetch("/api/user");
  const json: unknown = await res.json();

  if (!isUser(json)) {
    throw new Error("사용자 응답 형식이 올바르지 않습니다.");
  }

  return json;
}

실무에서 어떻게 쓰는지

API 응답, 웹훅, CSV import, 브라우저 저장소 데이터를 읽을 때 사용한다. 큰 프로젝트에서는 Zod 같은 검증 라이브러리를 쓰기도 하지만, 기본 원리는 같다. 알 수 없는 데이터는 unknown으로 받고 검증 후 좁힌다.

코드 리뷰 체크포인트

  • 외부 데이터를 바로 as SomeType으로 단정하지 않았는가?
  • 필수 필드의 존재와 타입을 확인하는가?
  • 실패 시 화면이나 호출부에서 처리할 수 있는 에러를 반환하는가?

3. React 이메일 폼 검증

연결 문제: https://nst21c.com/?task=reactFormValidation#labApp

흔한 실패 코드

function EmailForm() {
  const [email, setEmail] = useState("");

  return (
    <form>
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      <button>신청하기</button>
    </form>
  );
}

입력 상태는 있지만 제출 처리와 검증 흐름이 없다. 사용자가 잘못된 이메일을 입력해도 무엇이 문제인지 알 수 없고, form 기본 동작 때문에 페이지가 새로고침될 수도 있다.

왜 틀렸는지

폼은 단순히 값을 받는 UI가 아니라 사용자 실수를 줄이는 흐름이다. 입력값, 에러 메시지, 제출 가능 여부, 제출 중 상태가 함께 설계되어야 한다.

개선 코드

function EmailForm() {
  const [email, setEmail] = useState("");
  const [submitted, setSubmitted] = useState(false);

  const emailError =
    email.length === 0 ? "이메일을 입력해주세요." :
    !email.includes("@") ? "이메일 형식을 확인해주세요." :
    "";

  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    if (emailError) return;
    setSubmitted(true);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={email}
        onChange={(event) => setEmail(event.target.value)}
        aria-invalid={Boolean(emailError)}
      />
      {emailError && <p role="alert">{emailError}</p>}
      <button type="submit" disabled={Boolean(emailError)}>
        신청하기
      </button>
      {submitted && <p>신청이 접수되었습니다.</p>}
    </form>
  );
}

실무에서 어떻게 쓰는지

뉴스레터 신청, 문의 폼, 로그인, 회원가입, 결제 전 정보 입력 화면에서 쓴다. 실제 서비스에서는 이메일 형식뿐 아니라 중복 제출 방지, 서버 에러 표시, 성공 후 안내까지 같이 설계한다.

코드 리뷰 체크포인트

  • formonSubmit이 있고 preventDefault를 호출하는가?
  • 입력 중 에러와 제출 시 에러가 사용자에게 보이는가?
  • 버튼의 typedisabled 상태가 의도와 맞는가?
  • 성공/실패 후 사용자가 다음 행동을 알 수 있는가?

7일 복습 루틴

1일차: TypeScript 상태 모델링 문제를 풀고 boolean 상태를 union으로 바꿔본다. 2일차: unknown JSON 문제를 풀고 검증 함수를 직접 작성한다. 3일차: React 폼 검증 문제를 풀고 에러 메시지를 필드별로 나눈다. 4일차: 세 문제의 실패 코드를 다시 보고 왜 실패하는지 한 문장으로 적는다. 5일차: 각 예제를 실제 서비스 화면 하나에 연결해본다. 예: 검색 결과, 프로필, 뉴스레터 폼. 6일차: 코드 리뷰 체크포인트를 기준으로 자기 코드를 점검한다. 7일차: 같은 문제를 초기 코드 없이 다시 작성한다.

다음 추천 문제

  • TypeScript Promise 반환 타입: https://nst21c.com/?task=tsPromiseReturn#labApp
  • TypeScript API 응답 타입: https://nst21c.com/?task=tsApiResponse#labApp
  • React API 로딩 상태: https://nst21c.com/?task=reactLoadingState#labApp

이메일 발송 문구 초안

제목: 프론트엔드 실무 해설팩 01을 보내드립니다

본문:

안녕하세요. NST21C 코딩 실습 랩입니다.

신청해주신 프론트엔드 실무 해설팩 01입니다. 이 자료는 TypeScript 상태 모델링, unknown JSON 검증, React 이메일 폼 검증 예제를 기준으로 구성했습니다.

먼저 아래 문제 중 하나를 풀어본 뒤 자료를 읽으면 효과가 좋습니다.

이 자료는 정답 암기가 아니라 “왜 틀렸는지”와 “실무 코드에서는 어떻게 고치는지”를 확인하는 용도입니다.