🐨CoalaCoding
DocsExamplesTry itBoardB반
🐨CoalaCoding

개발자를 위한 한국어 웹 기술 문서

문서

  • JavaScript
  • Web Publishing
  • React
  • Python

커뮤니티

  • 게시판
  • 예제 모음
  • Try it 에디터

기타

  • GitHub
  • 관리자
© 2026 CoalaCoding. All rights reserved.
  • 22_무비앱-완료본
  • 01. GOFLIX 프로젝트 소개와 개발환경 설정
  • 02. React 진입점과 라우팅 설정
  • 03. Axios로 TMDB API 연결하기
  • 04. 공통 UI 컴포넌트 만들기 (UI.jsx)
  • 05. App.jsx — 레이아웃 구성과 데이터 가져오기
  • 06. Header와 Footer 만들기
  • 07. Home.jsx — 메인 페이지 완성하기
  • 08. Section과 Card — 영화 카드 목록 만들기
  • 09. MovieDetail — 영화 상세 페이지 만들기
  • 10. Category, ErrorPage 완성하기
  • 11. AI 챗봇 연동하기
  • 12_Swiper_캐러셀_적용과_프로젝트_마무리
  • 13. GOFLEX Gemini CLI 바이브코딩 프롬프트 템플릿
  • 00_시작하기
  • 01_App
  • 02_CSS
  • 03_Nav
  • 04_Hero
  • 05_AboutMe
  • 06_Projects
  • 07_Contact
  • 08_Footer
  • 09_완성_정리
  • 10_바이브코딩
  1. 홈
  2. 문서
  3. React
  4. 실전 프로젝트
  5. 05. App.jsx — 레이아웃 구성과 데이터 가져오기

05. App.jsx — 레이아웃 구성과 데이터 가져오기

헤더, 푸터, 데이터 로딩, Outlet을 활용한 전체 레이아웃을 만든다

코드 블록의 Try it Yourself 버튼으로 직접 실행할 수 있다.

구문

💡TIP

Gemini CLI로 구현하기 — App.jsx 레이아웃 & 데이터 로딩

  • 프롬프트: gemini "src/App.jsx를 작성해줘. TMDB API에서 movie/now_playing, tv/airing_today, movie/top_rated 데이터를 Promise.all로 동시에 가져와야 해. useState로 3개의 상태를 관리하고, useEffect로 앱 시작 시 한 번만 호출해줘. 가져온 데이터는 Outlet의 context로 하위 페이지에 전달하고, Header와 Footer도 포함해줘. axios 인스턴스는 [axios 인스턴스 경로]에서 가져와줘."
    • 사용 가이드: [axios 인스턴스 경로]를 ./api/axios로 바꾼다. API 엔드포인트가 다르면 tv/airing_today 부분을 수정한다.

1. App.jsx의 역할

App.jsx는 GOFLEX 앱의 뼈대 역할을 한다. 이 파일이 담당하는 일은 세 가지이다.

  • TMDB API에서 영화 데이터 3종(현재 상영, 인기, 최고 평점)을 가져온다
  • 상단에 Header, 하단에 Footer를 배치한다
  • 하위 페이지가 렌더링되는 Outlet에 데이터를 전달한다
순서만들 기능핵심 개념
1단계import + 상태 변수useState
2단계API 데이터 로딩Promise.all + useEffect
3단계화면 렌더링Outlet context

2. 1단계 — import + 상태 변수

src/App.jsx 파일을 열고 임시 코드를 모두 지운 뒤 아래를 작성한다.

import { useState, useEffect } from "react";
import { Outlet } from "react-router";
import { Header } from "./components/Header.jsx";
import { Footer } from "./components/Footer.jsx";
import api from "./api/axios";

export default function App() {
  const [now, setNow] = useState(null);
  const [popular, setPopular] = useState(null);
  const [topRated, setTopRated] = useState(null);
줄설명
1useState(유즈스테이트)와 useEffect(유즈이펙트) — React의 Hook(훅)이다. 훅은 컴포넌트에 특별한 능력을 부여하는 도구이다. useState는 컴포넌트 내부에 상태 값을 저장하고, 값이 바뀌면 화면을 자동으로 다시 렌더링하는 훅이다. useEffect는 컴포넌트에서 함수의 실행 시점을 제어하는 훅이다.
2Outlet(아울렛) — 리액트 라우터의 children path(패스)로 연결된 컴포넌트를 렌더링(렌더링)해준다.
3-4Header와 Footer를 별도 파일에서 가져온다. 다음 편에서 만든다.
53편에서 만든 TMDB API 인스턴스를 가져온다.
8-10영화 데이터를 담을 상태 변수 3개(now, popular, topRated)를 선언한다. 초기값은 모두 null(아직 아무것도 없음)이다.

3. 2단계 — API 데이터 로딩

같은 App.jsx 파일에서, const [topRated, ... 줄 바로 아래에 이어서 작성한다. 이 코드는 앱이 처음 실행될 때 TMDB 서버에 "현재 상영작, 인기작, 최고 평점 영화를 한꺼번에 보내줘"라고 요청하는 부분이다.

  async function loadMovie() {
    const [res1, res2, res3] = await Promise.all([
      api.get("movie/now_playing"),
      api.get("tv/airing_today"),
      //api.get("movie/popular"),
      api.get("movie/top_rated"),
    ]);
    setNow(res1.data.results.filter((m) => m.poster_path));
    setPopular(res2.data.results.filter((m) => m.poster_path));
    setTopRated(res3.data.results.filter((m) => m.poster_path));
  }

  useEffect(() => {
    loadMovie();
  }, []);
const [res1, res2, res3] = await Promise.all([
  api.get("movie/now_playing"),
  api.get("movie/popular"),
  api.get("movie/top_rated"),
]);

Promise.all(프로미스 올)은 여러 API 요청을 동시에 보내고, 모두 끝나면 결과를 한꺼번에 받는다.

방식요청 시간
순차 실행 (await 3번)1초 + 1초 + 1초 = 3초
Promise.all가장 느린 1개 기준 = 약 1초

요청 3개를 순서대로 보내면 각각의 응답을 기다려야 하지만, Promise.all로 동시에 보내면 모두 끝날 때까지 한 번만 기다리면 된다.


4. 3단계 — 화면 렌더링

같은 App.jsx 파일에서, useEffect 블록 바로 아래에 이어서 작성한다. 이 부분은 "데이터가 아직 도착하지 않았는지 확인하고, 도착한 데이터를 하위 페이지에 택배처럼 보내주는" 역할이다. <>(Fragment, 프래그먼트)는 여러 요소를 감싸되 실제 HTML 태그를 추가하지 않는 투명 포장지이다.

  const loading = now === null || popular === null || topRated === null;
  const ctx = {
    now: now || [],
    popular: popular || [],
    topRated: topRated || [],
    loading,
  };

  return (
    <>
      <Header />
      <Outlet context={ctx} />
      <Footer />
    </>
  );
}
줄설명
1세 상태 중 하나라도 null이면 아직 로딩 중이다.
2-7ctx 객체에 영화 데이터 3종과 loading 상태를 담는다. &#124;&#124;(OR 연산자)는 왼쪽 값이 null, undefined, 0 등 거짓으로 판단되는 값일 때 오른쪽 값을 반환한다. now가 아직 null이면 []를 반환하므로 하위 컴포넌트가 빈 배열을 안전하게 받을 수 있다.
11Header 컴포넌트를 상단에 배치한다.
12Outlet의 context에 ctx 객체를 전달한다. 하위 페이지(Home, MovieDetail 등)에서 이 데이터를 받아서 사용한다.
13Footer 컴포넌트를 하단에 배치한다.

5. 전체 코드

import { useState, useEffect } from "react";
import { Outlet } from "react-router";
import { Header } from "./components/Header.jsx";
import { Footer } from "./components/Footer.jsx";
import api from "./api/axios";

export default function App() {
  const [now, setNow] = useState(null);
  const [popular, setPopular] = useState(null);
  const [topRated, setTopRated] = useState(null);

  async function loadMovie() {
    const [res1, res2, res3] = await Promise.all([
      api.get("movie/now_playing"),
      api.get("tv/popular"),
      api.get("movie/top_rated"),
    ]);
    setNow(res1.data.results.filter((m) => m.poster_path));
    setPopular(res2.data.results.filter((m) => m.poster_path));
    setTopRated(res3.data.results.filter((m) => m.poster_path));
  }

  useEffect(() => {
    loadMovie();
  }, []);

  const loading = now === null || popular === null || topRated === null;
  const ctx = {
    now: now || [],
    popular: popular || [],
    topRated: topRated || [],
    loading,
  };

  return (
    <>
      <Header />
      <Outlet context={ctx} />
      <Footer />
    </>
  );
}

목차

  • 구문