useEffect, fetch()

2024. 11. 4. 16:26React

useEffectfetch()는 React에서 비동기 작업과 데이터 가져오기를 처리하는 데 핵심적인 역할을 합니다. 이 두 가지를 결합하면, 서버에서 데이터를 가져와 컴포넌트에 로드하는 것을 쉽게 구현할 수 있습니다.

1. useEffect란?

useEffectReact 훅 중 하나로, 컴포넌트가 렌더링될 때 특정 동작(사이드 이펙트)을 수행할 수 있게 해줍니다. useEffect비동기 데이터 가져오기, 구독 설정, DOM 조작, 타이머 설정컴포넌트 외부에서 데이터를 가져오거나 수정하는 작업을 할 때 주로 사용됩니다.

useEffect 기본 사용법

import React, { useEffect } from 'react';

function ExampleComponent() {
  useEffect(() => {
    // 컴포넌트가 마운트되었을 때 실행할 코드
    console.log("Component mounted");

    return () => {
      // 컴포넌트가 언마운트될 때 실행할 코드
      console.log("Component unmounted");
    };
  }, []);

  return <div>Example Component</div>;
}

useEffect의 매개변수

  • 첫 번째 매개변수: 효과 함수로, 컴포넌트가 렌더링될 때 실행할 코드를 작성합니다.
  • 두 번째 매개변수: 의존성 배열([])로, 이 배열에 지정된 값이 변경될 때만 효과 함수가 다시 실행됩니다. 비워둘 경우(빈 배열), 처음 마운트될 때 한 번만 실행됩니다.

useEffect의 작동 방식

  • 마운트 시 실행: 의존성 배열이 비어 있으면, useEffect는 컴포넌트가 마운트될 때 한 번만 실행됩니다.
  • 업데이트 시 실행: 의존성 배열에 상태나 props가 들어가면, 그 값이 변경될 때마다 useEffect가 다시 실행됩니다.
  • 언마운트 시 실행: 반환된 함수가 있다면, 컴포넌트가 언마운트될 때 그 함수가 실행됩니다. 이를 통해 클린업 작업을 수행할 수 있습니다.

2. fetch()란?

fetch()JavaScript의 내장 함수로, HTTP 요청을 통해 서버와 데이터를 주고받을 수 있는 기능을 제공합니다. fetch()Promise 기반이므로, 요청이 성공하면 데이터를 받고, 실패하면 오류를 반환합니다. 주로 REST API와 통신할 때 사용합니다.

fetch() 기본 사용법

fetch('https://api.example.com/data')
  .then((response) => response.json()) // JSON 형식으로 파싱
  .then((data) => {
    console.log(data); // 데이터 출력
  })
  .catch((error) => {
    console.error('Error fetching data:', error); // 오류 처리
  });

설명:

  • 첫 번째 인자: 요청을 보낼 URL입니다.
  • response.json(): 서버 응답이 JSON 형식일 때, 응답 데이터를 JSON으로 변환합니다. 이 작업도 비동기이므로 then으로 연결해야 합니다.
  • .catch(): 요청 중 오류가 발생하면 오류를 처리합니다.

3. useEffectfetch()를 결합하여 데이터 가져오기

React 컴포넌트가 렌더링될 때 서버에서 데이터를 가져와야 하는 경우, useEffectfetch()를 결합하여 비동기 데이터를 로드할 수 있습니다.

예제: useEffectfetch()를 사용하여 데이터 가져오기

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

function DataFetchingComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then((response) => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then((data) => {
        setData(data);
        setLoading(false);
      })
      .catch((error) => {
        setError(error.message);
        setLoading(false);
      });
  }, []);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <div>
      <h1>Data:</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

export default DataFetchingComponent;

설명:

  1. 상태 설정:
    • data: 서버에서 가져온 데이터를 저장하는 상태입니다.
    • loading: 데이터가 로딩 중인지 여부를 나타내는 상태입니다.
    • error: 데이터 가져오기 오류를 관리하는 상태입니다.
  2. useEffectfetch():
    • fetch()로 데이터를 가져오고, JSON으로 변환한 후 성공 시 data 상태에 저장합니다.
    • 오류가 발생하면 error 상태에 오류 메시지를 저장하고, 로딩 상태를 false로 업데이트하여 로딩이 끝났음을 나타냅니다.
  3. 렌더링 조건:
    • 로딩 중일 때는 Loading... 메시지를 표시합니다.
    • 오류가 있을 경우 오류 메시지를 출력합니다.
    • 데이터가 성공적으로 로드되면 JSON 데이터를 화면에 출력합니다.

4. 의존성 배열과 데이터 갱신

useEffect의존성 배열을 활용해 특정 상태나 props가 변경될 때 데이터 다시 가져오기를 할 수 있습니다. 의존성 배열에 변수를 넣으면, 해당 변수가 바뀔 때마다 useEffect가 실행됩니다.

예제: 의존성 배열을 사용하여 검색 기능 구현

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

function SearchComponent() {
  const [query, setQuery] = useState('react');
  const [results, setResults] = useState([]);

  useEffect(() => {
    fetch(`https://api.example.com/search?query=${query}`)
      .then((response) => response.json())
      .then((data) => setResults(data.results))
      .catch((error) => console.error('Error fetching data:', error));
  }, [query]); // query가 변경될 때마다 새 요청

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />
      <ul>
        {results.map((result) => (
          <li key={result.id}>{result.title}</li>
        ))}
      </ul>
    </div>
  );
}

설명:

  • query: 검색어를 저장하는 상태입니다. 사용자가 입력할 때마다 상태가 업데이트됩니다.
  • 의존성 배열: query가 변경될 때마다 새로운 검색어로 fetch 요청을 다시 보내 검색 결과를 업데이트합니다.
  • 검색 결과 출력: results 배열에 저장된 검색 결과를 반복(map)하여 화면에 표시합니다.

5. 비동기 fetchasync/await 사용

fetchPromise 기반이므로, async/await 구문을 사용하여 비동기 작업을 좀 더 직관적이고 간단하게 처리할 수 있습니다. 하지만, useEffect 내부에서는 비동기 함수(async function)를 직접 사용할 수 없기 때문에 useEffect 안에 비동기 함수를 따로 정의하여 호출해야 합니다.

예제: async/await로 데이터 가져오기

아래 예제에서는 async/await를 사용해 데이터를 불러오는 방법과 상태 관리, 에러 처리를 함께 구현해보겠습니다.

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

function AsyncDataFetching() {
  const [data, setData] = useState(null);  // 서버에서 가져온 데이터를 저장하는 상태
  const [loading, setLoading] = useState(true); // 로딩 상태
  const [error, setError] = useState(null); // 에러 상태

  useEffect(() => {
    // 비동기 함수 fetchData를 정의
    const fetchData = async () => {
      setLoading(true); // 로딩 상태를 true로 설정
      setError(null);   // 에러 상태 초기화

      try {
        const response = await fetch('https://api.example.com/data'); // 데이터 요청
        if (!response.ok) throw new Error('Network response was not ok'); // 상태 코드 확인

        const result = await response.json(); // JSON 데이터로 변환
        setData(result); // 데이터 상태에 저장
      } catch (error) {
        setError(error.message); // 에러 상태에 메시지 저장
      } finally {
        setLoading(false); // 로딩 상태를 false로 설정하여 완료 표시
      }
    };

    fetchData(); // 비동기 함수 호출
  }, []); // 의존성 배열을 비워 처음 마운트 시 한 번만 실행

  if (loading) return <p>Loading...</p>; // 로딩 중일 때
  if (error) return <p>Error: {error}</p>; // 에러가 있을 때

  return (
    <div>
      <h1>Fetched Data:</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

export default AsyncDataFetching;

코드 설명

  1. 상태 선언:
    • data: 서버에서 가져온 데이터를 저장하는 상태입니다.
    • loading: 데이터가 로드 중인지 여부를 나타내는 상태입니다.
    • error: 요청 중 발생한 오류를 관리하는 상태입니다.
  2. useEffect 내부의 비동기 함수:
    • fetchData 함수: async 함수로 정의하여 awaitfetch 요청을 처리합니다.
    • 에러 처리: 요청이 실패하거나 상태 코드가 200이 아닌 경우 에러 메시지를 error 상태에 저장하여 관리합니다.
    • 로딩 및 에러 초기화: fetchData 호출 시 setLoading(true)setError(null)로 로딩 상태와 에러 상태를 초기화합니다.
  3. 조건부 렌더링:
    • 로딩 중일 때는 "Loading..." 메시지를 표시합니다.
    • 에러가 발생했을 때는 오류 메시지를 보여줍니다.
    • 데이터가 성공적으로 로드된 경우 JSON 데이터를 화면에 출력합니다.

async/await를 사용한 fetch의 장점

  • 가독성 향상: thencatch 체인 없이 코드가 순차적으로 진행되어 더 읽기 쉬운 코드를 작성할 수 있습니다.
  • 에러 처리 일관성: try...catch 구문을 통해 에러를 일관되게 처리할 수 있습니다.

이렇게 async/awaitfetch를 결합하면 데이터를 직관적이고 명확하게 가져오고 처리할 수 있어, React에서 비동기 요청을 관리하는 데 매우 유용합니다. useEffect와 결합해 데이터 로딩, 상태 업데이트, 에러 처리 등을 함께 관리할 수 있어 효과적인 비동기 요청 처리 구조를 구축할 수 있습니다.

6. useEffectasync/await의 작동 방식

위 예제에서 비동기 함수useEffect 내부에서 정의하고 호출한 이유는, useEffect동기 함수만 직접 받을 수 있기 때문입니다. 따라서 비동기 작업을 useEffect 안에서 처리하려면 비동기 함수를 따로 정의해야 합니다.

추가 예제: async/await와 상태 업데이트를 통한 데이터 가져오기

이전 예제를 조금 더 확장하여, 상태 업데이트가 트리거될 때마다 데이터를 다시 가져오도록 만들어보겠습니다. 버튼 클릭 시마다 데이터를 갱신하는 경우를 예로 들어 설명하겠습니다.

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

function RefreshDataComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [refresh, setRefresh] = useState(0); // 새로고침을 위한 상태

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true); // 로딩 상태 설정
      setError(null);   // 오류 상태 초기화

      try {
        const response = await fetch('https://api.example.com/data');
        if (!response.ok) throw new Error('Network response was not ok');
        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error.message);
      } finally {
        setLoading(false); // 로딩 완료
      }
    };

    fetchData();
  }, [refresh]); // refresh 상태가 변경될 때마다 fetchData 함수가 실행됨

  const handleRefresh = () => {
    setRefresh((prev) => prev + 1); // refresh 상태 업데이트
  };

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <div>
      <h1>Fetched Data:</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
      <button onClick={handleRefresh}>Refresh Data</button>
    </div>
  );
}

export default RefreshDataComponent;

설명:

  • refresh 상태: refresh는 데이터 새로고침을 위해 사용되는 상태입니다. refresh가 변경되면 useEffect가 다시 실행됩니다.
  • handleRefresh 함수: 버튼 클릭 시 refresh 상태를 업데이트하여 useEffect가 다시 실행되도록 트리거합니다.
  • 상태 관리: data, loading, error 상태를 통해 비동기 작업의 진행 상태를 관리합니다.

7. useEffect에서 의존성 배열의 중요성

useEffect의존성 배열에 지정된 상태나 props가 변경될 때마다 실행됩니다. 올바른 의존성을 설정하지 않으면 의도하지 않은 재렌더링이나 무한 루프가 발생할 수 있습니다.

예제: 의존성 배열이 없는 경우와 있는 경우

// 무한 루프를 일으킬 수 있는 잘못된 예제
useEffect(() => {
  fetch('https://api.example.com/data')
    .then((response) => response.json())
    .then((data) => setData(data));
});

위 코드처럼 의존성 배열을 설정하지 않으면 컴포넌트가 렌더링될 때마다 useEffect가 반복 실행되어 무한 루프가 발생할 수 있습니다. 이를 방지하려면, 의존성 배열을 올바르게 설정해야 합니다.

useEffect(() => {
  fetch('https://api.example.com/data')
    .then((response) => response.json())
    .then((data) => setData(data));
}, []); // 빈 배열을 설정하여 처음 마운트될 때 한 번만 실행

의존성 배열을 활용한 데이터 업데이트 예제

useEffect(() => {
  fetch(`https://api.example.com/data/${id}`)
    .then((response) => response.json())
    .then((data) => setData(data));
}, [id]); // id가 변경될 때마다 useEffect가 실행됨

위 코드에서는 id가 변경될 때마다 새로운 데이터를 가져오도록 useEffect의 의존성 배열에 id를 추가했습니다.


8. useEffect의 클린업 함수

useEffect컴포넌트가 언마운트될 때나, 다음 렌더링이 발생하기 전에 정리(clean-up) 작업을 수행할 수 있는 클린업 함수를 반환할 수 있습니다. 클린업 함수는 타이머 제거, 이벤트 리스너 해제 등에 유용합니다.

예제: 타이머와 클린업 함수

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

function TimerComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setCount((prevCount) => prevCount + 1);
    }, 1000); // 1초마다 count를 증가시킴

    return () => {
      clearInterval(timer); // 컴포넌트가 언마운트될 때 타이머 제거
    };
  }, []);

  return <p>Timer: {count} seconds</p>;
}

export default TimerComponent;

설명:

  • setInterval: setInterval을 사용해 매 1초마다 count 상태를 증가시킵니다.
  • 클린업 함수: clearInterval을 호출하여 타이머를 제거함으로써 메모리 누수를 방지합니다. 이는 컴포넌트가 언마운트될 때 자동으로 실행됩니다.

9. fetch와 에러 처리 개선

실제 애플리케이션에서는 네트워크 오류나 잘못된 응답을 처리하는 것이 중요합니다. 다음은 fetch에서 응답 상태를 확인하여 에러 처리를 강화하는 방법입니다.

예제: fetch의 에러 처리와 상태 코드 확인

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

function DataComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      setError(null);

      try {
        const response = await fetch('https://api.example.com/data');

        if (!response.ok) { // 상태 코드가 200이 아닌 경우
          throw new Error(`Error: ${response.status} ${response.statusText}`);
        }

        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error.message); // 에러 메시지 설정
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <div>
      <h1>Data:</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

export default DataComponent;

설명:

  • !response.ok 조건: HTTP 상태 코드가 200이 아닌 경우, throw를 사용해 에러를 발생시킵니다. 이렇게 하면 404, 500 등의 에러가 발생했을 때 메시지를 사용자에게 전달할 수 있습니다.
  • 에러 상태 업데이트: error 상태를 통해 에러 메시지를 UI에 표시하여, 사용자에게 오류를 알릴 수 있습니다.

10. 데이터 의존성을 가진 useEffect 예제

여러 개의 상태에 의존하는 경우, 의존성 배열에 해당 상태들을 모두 추가하여 useEffect가 올바르게 동작하도록 설정할 수 있습니다.

예제: 의존성이 있는 useEffect

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

function UserProfile({ userId }) {
  const [userData, setUserData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchUserData = async () => {
      setLoading(true);

      try {
        const response = await fetch(`https://api.example.com/users/${userId}`);
        const data = await response.json();
        setUserData(data);
      } catch (error) {
        console.error('Error fetching user data:', error);
      } finally {
        setLoading(false);
      }
    };

    fetchUserData();
  }, [userId]); // userId가 변경될 때마다 데이터 재요청

  if (loading) return <p>Loading...</p>;

  return (
    <div>
      <h2>User Profile</h2>
      <p>Name:

 {userData?.name}</p>
      <p>Email: {userData?.email}</p>
    </div>
  );
}

export default UserProfile;

설명:

  • userId 의존성: userId가 변경될 때마다 useEffect가 실행되어, 새로운 사용자 데이터를 요청합니다.
  • 조건부 렌더링: 데이터가 로딩 중일 때는 Loading... 메시지를 표시하고, 로딩이 완료되면 사용자 정보를 출력합니다.

useEffectfetch는 React 컴포넌트에서 비동기 데이터 요청 및 관리를 구현하는 핵심 도구입니다. useEffect를 통해 컴포넌트의 상태와 외부 데이터를 유연하게 처리할 수 있으며, fetch와 결합하여 데이터를 불러오고 업데이트하는 로직을 쉽게 구현할 수 있습니다.

'React' 카테고리의 다른 글

POST(생성), useHistory  (0) 2024.11.04
custom Hooks  (0) 2024.11.04
react-router-dom  (0) 2024.11.04
json-server, RESTful API  (0) 2024.11.04
router  (0) 2024.11.04