React - useEffect()

 React Hook 중에는 useEffect라는 Hook이 존재한다.


 useEffect란 리액트에서 기본적으로 제공해주는 함수로써, useEffect함수가 포함된 컴포넌트가 처음 마운트되거나 컴포넌트가 리렌더링 될 때, 또는 선언된 변수의 값이 변경되거나 redux store의 값이 변경될 때 실행할 구문들을 정의해놓은 함수이다.

기본적으로 React에서는 state값이 변경되면 해당 컴포넌트가 다시 랜더링된다.

function Welcome(props) {

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

  return (
    <div>
	<button onClick={() => setCount(count + 1)}>증가</button>
      <p>count : {count}</p>
    </div>
  );
}

 

<참고사항>
 이때, <button onClick={setCount(count +1)}></button>
onClick에 함수의 참조를 넣는것이 아니라 위와 같이 함수 호출식을 그대로 넣어버리면
컴포넌트가 렌더링 될 때 해당 함수가 즉시 호출되어버리고
이는 state의 변경으로 인해 또 다시 렌더링되어 또 setCount가 호출되는
무한루프에 빠지게 된다. 

 

그런데 왜 굳이 useEffect라는 훅을 사용해서 state를 관리할까?
그건 다음과 같은 상황이 있기 때문이다.

1. 데이터 가져오기 (Fetching Data)

  컴포넌트가 처음 랜더링될 때 외부 API로부터 데이터를 가져오고 싶을 때


2. 구독 설정 (Setting up a Subscription)

  예를 들어, WebSocket 구독이나 이벤트 리스너 같은 것을 설정할 때

 

3. 수동 DOM 조작 (Manual DOM Manipulations)

  React 외부의 라이브러리와의 통합이 필요할 때, DOM을 수동으로 조작해야 하는 경우

4. 컴포넌트가 업데이트 될 때의 SideEffect 처리를 위해

 state나 props가 변경될 때마다 수행해야 하는 추가 작업이 있을 때

5. useEffect 내에서 반환된 함수는 컴포넌트가 언마운트될 때, 정리 작업 (Cleanup)시

 구독 해제, 메모리 해제 등의 정리 작업을 수행하기 위해

이러한 상황의 예제로 아래의 코드를 살펴보도록 하자.
아래 이미지는 페이지에서 useEffect를 통해 페이징 처리와 함께 쓰인 예제이다.

import React, {useEffect, useState} from 'react';
import axios from "axios";
import { Link } from 'react-router-dom';
import Pagination from "../Components/Pagination";
import './NewsListPage.css';
import {usePagination} from "../hooks/usePagination";


function NewsListPage() {
    const [newsList, setNewsList] = useState([]); // 뉴스 Data

    // 페이징 관련
    const [totalCount, setTotalCount] = useState(0);// 총 뉴스 Data 건수
    const { page, setPage, buttonRange, PAGE_SIZE } = usePagination(totalCount); // 페이징 관련 custom hook 사용

    useEffect(() => {
        fetchNewsList();
    }, [page]);

    async function fetchNewsList() {
        try {
            const response = await axios.get('/api/news/list', { params: { page: page, size: PAGE_SIZE } });
            if (response.status === 200) {
                setNewsList(response.data.newsList);
                setTotalCount(response.data.paging.total_count);
            }
        } catch (error) {
            console.error('Failed to fetch news list:', error);
        }
    }


    return (
        <div id="news_page_container">
            <h1 id="news_page_header"> 게임 뉴스 요약</h1>

            { newsList.map((news) => (
                <div key={news.news_id} className="news-card">
                    <Link to={`/news/detail/${news.news_id}`} className="news-link">
                        <img src={`/imgs/news/${news.news_id}.jpg`} alt="News" className="news-image"/>
                    </Link>
                    <div className="news-list-content">
                        <Link to={`/news/detail/${news.news_id}`} className="news-link">
                            <h4 className="news-list-title">{ news.news_title }</h4>
                        </Link>
                        <p className="news-desc">{ news.news_desc }</p>
                    </div>
                </div>
            )) }

            {/* 페이지네이션 */}
            <Pagination buttonRange={buttonRange} totalCount={totalCount} setPage={setPage} pageSize={PAGE_SIZE} />
        </div>
    );
}

export default NewsListPage;

 

 

위 코드를 보면

    useEffect(() => {
        fetchNewsList();
    }, [page]);

 이렇게 useEffect라는 Hook을 사용한 것을 볼 수 있는데, 이는 page의 변수 값이 바뀔 때마다 fetchNewList를 호출한다.
이처럼 useEffect는 두 개의 파라미터를 받는데

첫번째 파라미터는 함수가 들어가며 해당 함수에는 컴포넌트가 렌더링될 때 실행할 작업들을 작성한다.
두번째 파리미터는 [] 로  dependency라고 부르는데 해당 값에 의존하여 useEffect 함수를 실행할지 말지를 결정하기에 dependency라고 부르며 해당 dependency의 값이 바뀔 때 마다 useEffect 함수가 실행된다.

 fetchNewList에서는 서버로부터 Data를 받아와서  setNewsList, setTotalCount 를 실행시켜
newlist, totalcount의 상태를 업데이트 해준다.


이렇게 useEffect를 통해 컴포넌트의 라이프사이클과 데이터 페칭 로직을 효과적으로 분리할 수 있다.
 부가적으로 설명으로 page 들어가 있는 dependency 값을 비워둔다면
해당 컴포넌트가 처음으로 마운트 될 때만 실행된다.

useEffect(() => {
  ... // 실행할 내용들
}, []);