Front/React

useEffect, 생명주기 - React 배우기

oodada 2024. 3. 11. 00:55

라이프 사이클 (생명주기) 이해하기

React에서 컴포넌트의 생명주기를 관리하는 것은 앱의 성능과 유지보수성에 중요한 영향을 미칩니다.
특히, 함수형 컴포넌트에서는 useEffect 훅을 사용하여 이러한 관리 작업을 수행합니다.

- 뉴스 예제로 알아보는 라이프 사이클

데이터를 불러와 화면에 표시하는 간단한 뉴스 목록 컴포넌트를 생각해볼 수 있습니다.

생명주기 단계 소셜 미디어 대시보드
Mounting (마운팅) 데이터 가져오기
Updating (업데이팅) 실시간 업데이트
Unmounting (언마운팅) 컴포넌트가 DOM에서 제거되는 단계

마운트 (Mounting) - 태어나고

  • 이 컴포넌트는 useEffect 훅을 사용해 컴포넌트가 마운트될 때 뉴스 API에서 데이터를 한 번만 불러오고,
  • 이 데이터를 컴포넌트의 상태로 저장하여 화면에 뉴스 목록을 렌더링합니다.

업데이트 (Updating) - 성장하고

  • 사용자가 특정 카테고리를 선택하면,
  • 해당 카테고리에 맞는 뉴스 데이터를 다시 불러와 업데이트할 수 있습니다.

언마운트 (Unmounting) - 죽는다

  • 컴포넌트가 화면에서 사라지면, 뉴스 데이터를 불러오는 작업을 중지하고,
  • 불필요한 리소스를 해제하여 메모리 누수를 방지합니다.

이렇게 useEffect를 사용하면 API 호출 타이밍을 정확히 조절하여 필요할 때만 데이터를 불러올 수 있으므로 앱의 성능을 향상시키고 불필요한 데이터 요청을 줄일 수 있습니다.

리액트 컴포넌트의 생명주기에 따라 특정한 작업을 수행하고 싶을 때, useEffect 훅을 사용할 수 있습니다.


- 페이지가 마운팅, 업데이트, 언마운팅될 때 body의 배경색을 변경하는 코드

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

function App() {
    const [color, setColor] = useState('blue')

    // 마운트시에만 실행
    useEffect(() => {
        console.log('마운트시에만 실행')
        // body에 배경색 추가
        document.body.style.backgroundColor = 'blue'

        // 언마운트시에만 실행
        return () => {
            // body에 배경색 제거
            document.body.style.backgroundColor = ''
        }
    }, [])

    // color 상태 업데이트시에만 실행
    useEffect(() => {
        console.log('업데이트시에만 실행')
        // body에 배경색 추가
        document.body.style.backgroundColor = color
    }, [color])

    return (
        <div>
            <div>안녕하세요</div>
            <button onClick={'yellow'}>색상 변경</button>
        </div>
    )
}

export default App

useEffect 로 라이프 사이클 제어하기

useEffect 훅을 사용하면 컴포넌트의 생명주기에 따라 특정한 작업을 수행할 수 있습니다.
useEffect 훅을 사용하여 컴포넌트가 마운트될 때, 업데이트될 때, 언마운트될 때 특정한 작업을 수행할 수 있습니다.

생명주기 단계 소셜 미디어 대시보드
Mounting (마운팅) 데이터 가져오기
Updating (업데이팅) 실시간 업데이트
Unmounting (언마운팅) 컴포넌트가 DOM에서 제거되는 단계

- 마운트 제어하기

useEffect(() => {...}, [])

의존성 배열을 빈 배열로 전달하면, useEffect의 콜백함수는 컴포넌트가 마운트될 때만 실행됩니다.
ex) 뉴스 데이터를 불러오는 작업을 컴포넌트가 마운트될 때만 실행

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

export default function App() {
    const [count, setCount] = useState(0)

    // useEffect(() => {...}, []) 의존성 배열로 빈 배열을 전달하면
    // 컴포넌트가 마운트될 때만 useEffect의 콜백함수가 실행됩니다.
    useEffect(() => {
        console.log('마운트될 때만 실행')
    }, [])

    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>Click me</button>
        </div>
    )
}

- 마운트 & 업데이트 제어하기

useEffect(() => {...})

의존성 배열을 전달하지 않으면, useEffect의 콜백 함수는 컴포넌트가 마운트될 때와 업데이트될 때 모두 실행됩니다.

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

export default function App() {
    const [count, setCount] = useState(0)

    // useEffect(() => {...}) 의존성 배열을 전달하지 않았으므로,
    // 콜백 함수는 마운트 시점에도 실행되고,
    // 업데이트 시점에도 실행됩니다.
    useEffect(() => {
        console.log('마운트, 업데이트시 실행')
    })

    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>Click me</button>
        </div>
    )
}

- 업데이트 제어하기

useEffect(() => {...}, [count])

의존성 배열에 특정한 상태를 전달하면, 해당 상태가 변경될 때만 useEffect의 콜백 함수가 실행됩니다.
ex) 카테고리가 변경될 때만 뉴스 데이터를 불러오는 경우

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

export default function App() {
    const [count, setCount] = useState(0)

    // useRef 훅을 사용하여 컴포넌트의 변수 didMountRef를 생성
    // 현재 컴포넌트가 마운트했는지 판단하는 변수 didMountRef를 Ref 객체로 생성
    // 초기값은 false
    const didMountRef = useRef(false)

    // 의존성 배열로 아무것도 전달하지 않았으므로, 콜백 함수는 마운트 시점에도 실행되고, 업데이트 시점에도 실행됩니다.
    useEffect(() => {
        // !didMountRef.current는 didMountRef.current가 false일 때 true를 반환하고, true일 때 false를 반환합니다.
        // 마운트 시점에는 조건(!false=true)은 참이므로, if문 안의 코드가 실행되고 didMountRef.current를 true로 설정합니다.
        // 이후에 컴포넌트가 업데이트될 때는 조건(!true=false)은 거짓이므로, console.log('업데이트될 때만 실행')이 실행됩니다.
        if (!didMountRef.current) {
            // didMountRef.current를 true로 설정 후 함수를 종료
            didMountRef.current = true
            return
        } else {
            console.log('업데이트될 때만 실행')
        }
    })

    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>Click me</button>
        </div>
    )
}

컴포넌트 언마운트와 클린업 함수

useEffect(return () => {...})

컴포넌트가 언마운트되면, React는 해당 컴포넌트와 관련된 모든 리소스를 해제하려고 시도합니다. 이 과정에서 useEffect 내에서 반환하는 클린업 함수가 중요한 역할을 합니다.

- 클린업 함수의 역할

useEffect 내에서 반환하는 클린업 함수는 컴포넌트가 언마운트될 때 실행됩니다. 이 함수는 메모리 누수를 방지하고, 필요하지 않은 작업이 백그라운드에서 계속 실행되는 것을 막기 위해 사용됩니다. 특히, setInterval과 같은 타이머를 사용할 때 이 클린업 과정이 중요합니다.

컴포넌트의 "언마운트"는 컴포넌트가 더 이상 화면에 표시되지 않을 때 발생합니다.

- 클린업 함수 예제

import { useEffect, useState } from 'react'

const Timer = () => {
    const [timer, setTimer] = useState(0)
    const [isShow, setIsShow] = useState(true)

    useEffect(() => {
        let interval

        if (isShow) {
            interval = setInterval(() => {
                setTimer((prev) => prev + 1)
            }, 1000)
        }
        // 타이머를 시작하는 interval을 설정합니다.

        // 이 리턴 함수는 컴포넌트가 언마운트 될 때 호출됩니다.
        // 컴포넌트가 제거되면 interval도 정리해야 하기 때문입니다.
        return () => {
            console.log('타이머 정리')
            clearInterval(interval)
        }
    }, [isShow]) // 종속성 배열이 비어 있으므로 마운트 시에만 실행

    const hideTimer = () => {
        setIsShow(false)
        setTimer(0) // 타이머 리셋
    }

    return (
        <div>
            {isShow ? (
                <div>
                    <h2>{timer}초</h2>
                    <button onClick={hideTimer}>타이머 숨기기</button>
                </div>
            ) : (
                <button onClick={() => setIsShow(true)}>타이머 보이기</button>
            )}
        </div>
    )
}

export default Timer

localStorage를 사용한 counter 데이터 저장

  • localStorage는 브라우저에 데이터를 저장하는 방법 중 하나로, 쿠키와 비슷한 역할을 합니다.
  • localStorage에 저장된 데이터는 브라우저를 닫아도 유지되며, 브라우저를 다시 열 때에도 데이터가 유지됩니다.
  • localStorage에 저장된 데이터는 문자열 형태로 저장되므로, 객체를 저장할 때는 JSON.stringify로 문자열로 변환하여 저장하고, 불러올 때는 JSON.parse로 객체로 변환하여 사용합니다.

- localStorage 사용 예제

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

const Counter = () => {
    const [count, setCount] = useState(0)

    // localStorage에 저장된 count 값을 불러와 초기값으로 설정
    useEffect(() => {
        const savedCount = localStorage.getItem('count')
        if (savedCount) {
            setCount(Number(savedCount))
        }
    }, [])

    // count 값이 변경될 때마다 localStorage에 저장
    useEffect(() => {
        localStorage.setItem('count', count)
    }, [count])

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={() => setCount(count + 1)}>+1</button>
            <button onClick={() => setCount(count - 1)}>-1</button>
        </div>
    )
}

export default Counter

useEffect 뉴스 카테고리 선택 기능 구현하기

  • 이 컴포넌트는 useEffect 훅을 사용해 컴포넌트가 마운트될 때 뉴스 API에서 데이터를 한 번만 불러오고,
  • 이 데이터를 컴포넌트의 상태로 저장하여 화면에 뉴스 목록을 렌더링합니다.
  • 만약 사용자가 특정 카테고리를 선택하면,
  • 해당 카테고리에 맞는 뉴스 데이터를 다시 불러와 업데이트할 수 있습니다.
"use client";

import axios from "axios";
import Image from "next/image";
import Link from "next/link";
import React, { useEffect, useState } from "react";

const NewsPage = () => {
  const [news, setNews] = useState([]);
  const [categories, setCategories] = useState([]);
  const [selectedCategory, setSelectedCategory] = useState("");

  useEffect(() => {
    const fetchNews = async () => {
      try {
        const response = await axios.get(
          "https://content.guardianapis.com/search?api-key=cb5c8f1d-41e3-4481-b13e-7b075cf3e537&show-fields=thumbnail,headline,byline,bodyText"
        );
        const results = response.data.response.results;
        setNews(results);

        // 카테고리 목록 추출
        const uniqueCategories = [
            // 새로운 Set 객체를 생성하여 중복을 제거한 카테고리 목록을 생성
          ...new Set(results.map((item) => item.sectionId)),
        ];
        // 고유한 카테고리 목록을 상태로 저장
        setCategories(uniqueCategories);

        } catch (error) {
            console.error(error);
        }
    };

        fetchNews();
    }, []);

    // 카테고리 선택시 뉴스 필터링
  const filteredNews =
    selectedCategory === ""
      ? news
      : news.filter((item) => item.sectionId === selectedCategory);

  return (
    <div>
      <h2>뉴스</h2>

      {/* 카테고리 필터 */}
      <div className="flex gap-2 px-5 mb-4">
        <button
          onClick={() => setSelectedCategory("")}
          className={`px-4 py-2 rounded ${
            selectedCategory === "" ? "bg-blue-500 text-white" : "bg-gray-200"
          }`}
        >
          전체
        </button>
        {categories.map((category) => (
          <button
            key={category}
            onClick={() => setSelectedCategory(category)}
            className={`px-4 py-2 rounded ${
              selectedCategory === category
                ? "bg-blue-500 text-white"
                : "bg-gray-200"
            }`}
          >
            {category}
          </button>
        ))}
      </div>

      <ul className="divide-y px-5">
        {filteredNews.map((item) => (
          <li key={item.id}>
            <Link href={item.webUrl} className="py-3 flex gap-4">
              <Image
                src={item.fields.thumbnail}
                alt={item.webTitle}
                width={300}
                height={200}
                className="object-cover rounded w-1/3"
              />
              <div className="flex-1 flex flex-col justify-between">
                <strong>{item.webTitle}</strong>
                <p className="text-gray-500 text-sm mt-2">
                  {item.webPublicationDate}
                </p>
              </div>
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default NewsPage;
  1. results.map((item) => item.sectionId)
  • 작업: results 배열에서 각 객체의 sectionId 값을 가져옵니다.
  • 결과: map 함수는 새로운 배열을 반환하며, 배열의 각 요소는 sectionId 값이 됩니다.
const results = [
  { id: 1, sectionId: "sports", webTitle: "Sports News 1" },
  { id: 2, sectionId: "business", webTitle: "Business News 1" },
  { id: 3, sectionId: "sports", webTitle: "Sports News 2" },
];

const sectionIds = results.map((item) => item.sectionId);
console.log(sectionIds); // ["sports", "business", "sports"]
  1. new Set(results.map((item) => item.sectionId))
  • 작업: 배열을 Set 객체로 변환합니다.
  • 결과: Set은 중복된 값을 자동으로 제거하므로 고유한 값들만 남습니다.
const sectionIds = ["sports", "business", "sports"];
const uniqueCategories = [...new Set(sectionIds)];
console.log(uniqueCategories); // ["sports", "business"]
  1. [...new Set(results.map((item) => item.sectionId))]
  • 작업: Set 객체를 다시 배열로 변환합니다.
  • 결과: 고유한 값들을 포함한 배열이 생성됩니다.
  • ...는 스프레드 연산자로, Set의 각 요소를 배열에 펼칩니다.
const uniqueSet = new Set(["sports", "business"]);
const uniqueArray = [...uniqueSet];
console.log(uniqueArray); // ["sports", "business"]
티스토리 친구하기