Next.js 기초 입문 7주차: 데이터 페칭 기초 (Client-side)

📚
제7주차 학습 목표

데이터 페칭 기초 (Client-side)

클라이언트 측에서 데이터를 가져오는 방법을 학습합니다.

Next.js 기초 입문: 7회차 데이터 페칭 기초 (Client-side)

안녕하세요! Next.js 기초 입문 과정의 7회차에 오신 것을 환영합니다. 이번 회차에서는 웹 애플리케이션에서 가장 중요한 기능 중 하나인 데이터 페칭(Data Fetching), 그중에서도 클라이언트 측(Client-side)에서 데이터를 가져오는 기본적인 방법에 대해 학습하겠습니다. 사용자 경험을 향상시키고 동적인 웹 페이지를 구현하는 데 필수적인 지식이므로, 집중해서 따라와 주시길 바랍니다.

📌 이번 회차 학습 목표

  • 클라이언트 측 데이터 페칭의 개념과 필요성을 설명할 수 있습니다.
  • React의 useEffect 훅을 사용하여 컴포넌트 생명주기에 맞춰 데이터를 가져올 수 있습니다.
  • 브라우저의 내장 fetch API를 사용하여 서버로부터 데이터를 요청하고 응답을 처리할 수 있습니다.
  • 가져온 데이터를 컴포넌트 상태(state)에 저장하고 UI에 렌더링할 수 있습니다.
  • 클라이언트 측 데이터 페칭 시 발생할 수 있는 일반적인 문제점과 주의사항을 이해합니다.

📝 개념 설명

클라이언트 측 데이터 페칭이란?

클라이언트 측 데이터 페칭(Client-side Data Fetching)은 웹 브라우저(클라이언트)가 페이지를 로드한 후, JavaScript 코드를 실행하여 추가적인 데이터를 서버로부터 비동기적으로 가져오는 방식을 의미합니다. Next.js와 같은 프레임워크에서는 서버 측 렌더링(SSR)이나 정적 사이트 생성(SSG)을 통해 초기 페이지를 빠르게 제공하지만, 사용자 인터랙션에 따라 동적으로 변경되거나 추가적인 정보가 필요한 경우에는 클라이언트 측에서 데이터를 가져오는 것이 일반적입니다.

 

예를 들어, 사용자가 ‘더 보기’ 버튼을 클릭하거나, 검색 필터 값을 변경할 때, 또는 특정 탭을 선택했을 때 새로운 데이터를 서버에 요청하여 화면을 업데이트하는 경우가 이에 해당합니다. 이 방식은 초기 로드 시간을 단축하고, 필요한 데이터만 그때그때 가져와서 네트워크 트래픽을 효율적으로 관리할 수 있다는 장점이 있습니다.

useEffect 훅의 활용

React 컴포넌트에서 클라이언트 측 데이터 페칭을 수행할 때 가장 일반적으로 사용되는 훅은 useEffect입니다. useEffect는 함수 컴포넌트 내에서 부수 효과(side effects)를 수행할 수 있게 해주는 훅입니다. 데이터 페칭은 네트워크 요청이라는 부수 효과에 해당하므로, useEffect 내에서 처리하는 것이 적절합니다.

useEffect의 기본 구조:

import React, { useEffect } from 'react';

useEffect(() => {
  // 부수 효과(데이터 페칭 등) 로직
  return () => {
    // 클린업(cleanup) 함수 (선택 사항)
  };
}, [의존성 배열]); // 의존성 배열
  • 첫 번째 인자 (함수): 컴포넌트가 렌더링된 후 실행될 로직을 담습니다.
  • 두 번째 인자 (의존성 배열): 배열 내의 값이 변경될 때만 첫 번째 인자의 함수가 다시 실행됩니다.
  • 의존성 배열이 빈 배열([])이면, 컴포넌트가 마운트(처음 렌더링)될 때 한 번만 실행됩니다. 데이터 페칭 시 주로 사용됩니다.
  • 의존성 배열을 생략하면, 컴포넌트가 렌더링될 때마다 매번 실행됩니다.

fetch API로 데이터 가져오기

웹 브라우저에는 서버와 통신하여 데이터를 주고받을 수 있는 내장 API인 fetch API가 있습니다. fetch API는 Promise 기반으로 동작하며, HTTP 요청을 보내고 응답을 받는 과정을 간결하게 처리할 수 있게 해줍니다. 과거에는 XMLHttpRequestAxios와 같은 라이브러리를 많이 사용했지만, 현대 웹 개발에서는 fetch API가 표준처럼 사용되고 있습니다.

fetch API의 기본 사용법:

fetch('요청할_URL')
  .then(response => response.json()) // 응답을 JSON 형태로 파싱
  .then(data => console.log(data)) // 파싱된 데이터 처리
  .catch(error => console.error('데이터 페칭 오류:', error)); // 오류 처리
  • fetch() 함수는 첫 인자로 요청할 URL을 받습니다. 기본적으로 GET 요청을 수행합니다.
  • .then() 메서드를 사용하여 Promise가 성공적으로 해결되었을 때의 콜백 함수를 등록합니다.
  • 첫 번째 .then()에서는 응답 객체(Response)를 받으며, response.json() 메서드를 호출하여 응답 본문을 JSON 형태로 파싱합니다. 이 또한 Promise를 반환합니다.
  • 두 번째 .then()에서는 파싱된 실제 데이터를 받아서 처리합니다.
  • .catch() 메서드를 사용하여 네트워크 오류나 서버 응답 오류 등 발생 가능한 예외를 처리합니다.
🔄 클라이언트 측 데이터 페칭 흐름
컴포넌트 렌더링
useEffect 실행 (마운트 시)
fetch API로 데이터 요청
서버 응답 대기
데이터 수신 및 파싱
useState로 상태 업데이트
컴포넌트 리렌더링 (UI 업데이트)

💡 예제 & 실습

가상의 API에서 사용자 목록을 가져와 화면에 표시하는 예제를 통해 클라이언트 측 데이터 페칭 과정을 실습해 보겠습니다. 이 예제에서는 JSONPlaceholder라는 무료 가짜 API를 사용합니다.

단계 1: Next.js 프로젝트 설정 (선택 사항, 기존 프로젝트 사용 가능)

새로운 Next.js 프로젝트를 시작하려면 다음 명령어를 사용합니다.

npx create-next-app@latest my-data-fetching-app --typescript --eslint --app
cd my-data-fetching-app
npm run dev

이후 app/page.tsx 파일을 수정하여 실습을 진행합니다.

단계 2: 사용자 목록을 가져오는 컴포넌트 작성

app/page.tsx 파일을 열고 아래와 같이 코드를 작성합니다.

'use client'; // 클라이언트 컴포넌트임을 명시

import React, { useEffect, useState } from 'react';

interface User {
  id: number;
  name: string;
  email: string;
}

export default function HomePage() {
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/users');
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data: User[] = await response.json();
        setUsers(data);
      } catch (err) {
        if (err instanceof Error) {
          setError(err.message);
        } else {
          setError('An unknown error occurred');
        }
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();
  }, []); // 빈 의존성 배열: 컴포넌트 마운트 시 한 번만 실행

  if (loading) {
    return <div>사용자 정보를 불러오는 중...</div>;
  }

  if (error) {
    return <div style={{ color: 'red' }}>오류 발생: {error}</div>;
  }

  return (
    <div style={{ padding: '20px' }}>
      <h1>사용자 목록</h1>
      <ul>
        {users.map(user => (
          <li key={user.id} style={{ marginBottom: '10px' }}>
            <strong>이름:</strong> {user.name} <br />
            <strong>이메일:</strong> {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
}

코드 해설:

  1. 'use client';: Next.js 13의 App Router에서는 기본적으로 서버 컴포넌트입니다. 브라우저 환경에서 useState, useEffect와 같은 React 훅을 사용하려면 파일 상단에 'use client'; 지시어를 추가하여 클라이언트 컴포넌트임을 명시해야 합니다.
  2. 상태 관리 (useState):
    • users: 가져온 사용자 데이터를 저장할 배열 상태입니다. 초기값은 빈 배열입니다.
    • loading: 데이터 페칭 중임을 나타내는 불리언 상태입니다. 초기값은 true입니다.
    • error: 데이터 페칭 중 오류가 발생했을 때 오류 메시지를 저장할 상태입니다. 초기값은 null입니다.
  3. 데이터 페칭 (useEffect):
    • useEffect 훅 내부에 비동기 함수 fetchUsers를 정의했습니다. async/await 문법을 사용하여 비동기 코드를 동기 코드처럼 읽기 쉽게 작성했습니다.
    • fetch('https://jsonplaceholder.typicode.com/users'): JSONPlaceholder API의 /users 엔드포인트로 GET 요청을 보냅니다.
    • if (!response.ok) { ... }: HTTP 응답 코드가 200번대(성공)가 아닐 경우 오류를 발생시킵니다.
    • const data: User[] = await response.json();: 응답 본문을 JSON 형태로 파싱합니다.
    • setUsers(data);: 성공적으로 가져온 데이터를 users 상태에 저장합니다.
    • catch (err) { ... }: 네트워크 오류나 서버 응답 오류 발생 시 error 상태를 업데이트합니다.
    • finally { setLoading(false); }: 데이터 페칭 성공 여부와 관계없이 로딩 상태를 false로 변경합니다.
    • [] (빈 의존성 배열): 이펙트 함수는 컴포넌트가 처음 마운트될 때 한 번만 실행됩니다.
  4. 조건부 렌더링:
    • loading 상태가 true일 때는 ‘사용자 정보를 불러오는 중…’ 메시지를 표시합니다.
    • error 상태에 값이 있을 때는 오류 메시지를 표시합니다.
    • 두 경우 모두 해당하지 않을 때(즉, 데이터 로딩이 완료되고 오류가 없을 때) 사용자 목록을 렌더링합니다.
⚖️ 서버 컴포넌트 vs 클라이언트 컴포넌트 (데이터 페칭 관점)
항목 서버 컴포넌트 (기본) 클라이언트 컴포넌트 ('use client')
데이터 페칭 시점 서버에서 렌더링되기 전/중에 데이터 페칭 브라우저에서 컴포넌트가 마운트된 후 (useEffect)
주요 API/훅 fetch() (직접 사용), async/await useEffect, useState, fetch()
장점 초기 로드 빠름, SEO 유리, 보안 강화 (API 키 노출 X) 사용자 인터랙션에 따른 동적 데이터 업데이트, 작은 번들 크기
단점 사용자 인터랙션에 따른 동적 데이터 업데이트 어려움 초기 로드 시 데이터 로딩 지연 가능성, SEO 불리 (초기 데이터 없으면)
적합한 상황 초기 페이지 로드 시 필요한 데이터, SEO 중요한 페이지 사용자 로그인 후 대시보드, 검색 필터링, 무한 스크롤 등 동적 상호작용

⚠️ 자주 틀리는 것 / 주의사항

  • 'use client' 누락: Next.js 13의 App Router에서 useState, useEffect와 같은 클라이언트 측 훅을 사용하려면 반드시 파일 상단에 'use client';를 선언해야 합니다. 이를 잊으면 오류가 발생합니다.
  • 의존성 배열 누락 또는 잘못된 설정: useEffect의 두 번째 인자인 의존성 배열을 빈 배열([])로 두지 않으면, 컴포넌트가 리렌더링될 때마다 데이터 페칭이 무한히 반복될 수 있습니다. 반대로, 의존성 배열에 필요한 값을 포함하지 않으면 최신 상태를 참조하지 못하는 클로저 문제가 발생할 수 있습니다.
  • 오류 처리 누락: 네트워크 요청은 언제든지 실패할 수 있습니다. try...catch 블록을 사용하여 오류를 적절히 처리하고 사용자에게 피드백을 제공하는 것이 중요합니다.
  • 로딩 상태 관리: 데이터 페칭 중에는 사용자에게 로딩 스피너나 메시지를 보여주어 UX를 개선해야 합니다. 데이터가 아직 없는 상태에서 데이터를 사용하려고 하면 런타임 오류가 발생할 수 있습니다.
  • 클린업 함수: 컴포넌트가 언마운트되기 전에 실행될 클린업 함수를 useEffect에서 반환할 수 있습니다. 예를 들어, 진행 중인 네트워크 요청을 취소하여 메모리 누수를 방지하는 데 사용됩니다. 이 예제에서는 간단하여 생략했지만, 복잡한 상황에서는 고려해야 합니다.

🔗 다음 회차 예고

이번 회차에서는 클라이언트 측 데이터 페칭의 기본을 다루었습니다. 다음 8회차에서는 Next.js가 제공하는 강력한 데이터 페칭 방식인 서버 컴포넌트에서의 데이터 페칭에 대해 심층적으로 학습할 예정입니다. 서버 컴포넌트가 무엇인지, 어떻게 서버에서 직접 데이터를 가져와 렌더링하는지, 그리고 클라이언트 컴포넌트와의 차이점과 사용 시나리오를 비교 분석하며 Next.js의 진정한 강점을 이해하게 될 것입니다. 기대해 주세요!

✅ 이번 회차 핵심 정리
  • 클라이언트 측 데이터 페칭은 브라우저에서 컴포넌트가 렌더링된 후 JavaScript를 통해 데이터를 가져오는 방식입니다.
  • React의 useEffect 훅은 컴포넌트 마운트 시 데이터 페칭과 같은 부수 효과를 처리하는 데 사용됩니다.
  • 브라우저 내장 fetch API는 Promise 기반으로 HTTP 요청을 보내고 응답을 처리하는 표준 방법입니다.
  • 데이터 페칭 시 useState를 사용하여 로딩, 오류, 데이터 상태를 관리하고 조건부 렌더링으로 UI를 업데이트하는 것이 중요합니다.
  • 'use client' 지시어는 Next.js App Router에서 클라이언트 측 훅을 사용하기 위해 필수적입니다.

댓글 남기기