Front/React

useEffect, 생명주기 - React 배우기

oodada 2024. 3. 11. 00:55

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

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

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

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

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

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


리액트 컴포넌트는 생성, 갱신, 제거의 세 가지 생명주기를 가지고 있습니다. 이 세 가지 생명주기에 따라 컴포넌트의 상태를 변경하거나, 화면에 렌더링하는 작업을 수행할 수 있습니다.

생명주기 단계 설명
Mounting (마운팅) 컴포넌트가 DOM에 삽입되는 초기 단계
Updating (업데이팅) 컴포넌트가 prop 또는 state의 변경에 응답하여 업데이트되는 단계
Unmounting (언마운팅) 컴포넌트가 DOM에서 제거되는 단계

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

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

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

function App() {
    const [color, setColor] = useState('yellow') // 초기 색상을 'yellow'로 설정

    // 컴포넌트가 마운팅될 때, 업데이트될 때, 언마운팅될 때 실행되는 useEffect

    useEffect(() => {
        // Mounting
        // 페이지가 마운팅될 때 body의 배경색을 'color' 상태의 값으로 설정
        document.body.style.backgroundColor = color
        document.body.id = 'body'
        document.body.classList.add(color)

        // Unmounting
        // 페이지가 언마운팅될 때 body의 배경색을 흰색으로 변경, id 제거, class 제거
        return () => {
            document.body.style.backgroundColor = 'white'
            document.body.id = ''
            document.body.classList.remove(color)
        }
    }, [color]) // 'color' 상태가 변할 때마다 이 useEffect가 실행됩니다.

    // Updating
    useEffect(() => {
        console.log(`색상이 ${color}로 변경되었습니다.`)
    }, [color]) // 'color' 상태가 변할 때마다 이 useEffect가 실행됩니다.

    // 버튼 클릭 시 'color' 상태를 토글하는 함수
    const toggleColor = () => {
        setColor((prevColor) => (prevColor === 'yellow' ? 'blue' : 'yellow'))
    }

    return (
        <div>
            <div>안녕하세요</div>
            <button onClick={toggleColor}>색상 변경</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

useEffect 뉴스 예제

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

- API 키 발급

  • News API에서 API 키를 발급받아 사용합니다.

- fetch로 API 호출하기

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

function News() {
    const [news, setNews] = useState([]) // 뉴스 데이터를 저장할 상태
    const [category, setCategory] = useState('all') // 카테고리를 저장할 상태

    // useEffect를 사용하여 컴포넌트가 마운트될 때, 업데이트될 때, 언마운트될 때 뉴스 데이터를 불러오는 작업을 수행
    useEffect(() => {
        // async 함수를 만들어 useEffect 내부에서 호출
        const fetchData = async () => {
            let response
            try {
                // 카테고리가 'all'이면 모든 뉴스 데이터를 불러옴
                if (category === 'all') {
                    response = await fetch('https://newsapi.org/v2/top-headlines?country=kr&apiKey=API_KEY')
                } else {
                    // 카테고리가 'all'이 아니면 해당 카테고리의 뉴스 데이터를 불러옴
                    response = await fetch(
                        `https://newsapi.org/v2/top-headlines?country=kr&category=${category}&apiKey=API_KEY`
                    )
                }
                const data = await response.json() // JSON 형태로 변환
                setNews(data.articles) // 뉴스 데이터를 상태에 저장 (articles 배열)
            } catch (error) {
                console.error(error)
            }
        }

        fetchData() // fetchData 함수를 호출
    }, [category]) // 'category' 상태가 변할 때마다 이 useEffect가 실행됩니다.

    // 카테고리 버튼을 클릭하면 'category' 상태를 변경하는 함수
    const selectCategory = (category) => {
        setCategory(category)
    }

    return (
        <div>
            <h1>뉴스</h1>
            <button onClick={() => selectCategory('all')}>전체</button>
            <button onClick={() => selectCategory('business')}>비즈니스</button>
            <button onClick={() => selectCategory('entertainment')}>엔터테인먼트</button>
            <button onClick={() => selectCategory('health')}>건강</button>
            <button onClick={() => selectCategory('science')}>과학</button>
            <button onClick={() => selectCategory('sports')}>스포츠</button>
            <button onClick={() => selectCategory('technology')}>기술</button>
            <ul>
                {news.map((article, index) => (
                    <li key={index}>
                        <a href={article.url} target="_blank" rel="noreferrer">
                            {article.title}
                        </a>
                    </li>
                ))}
            </ul>
        </div>
    )
}

export default News

- axios로 API 호출하기

axios는 HTTP 요청을 보내는 라이브러리로, fetch 함수보다 더 간단하고 편리하게 사용할 수 있다.

  • axios는 HTTP 클라이언트 라이브러리로, 브라우저와 Node.js 환경에서 모두 사용할 수 있습니다.
  • axios를 사용하면 간단하게 API 호출을 할 수 있으며, Promise 기반의 API를 제공합니다.
npm install axios
yarn add axios
import React, { useEffect, useState } from 'react'
import axios from 'axios'

function News() {
    const categoryList = [
        { name: '전체', value: 'all' },
        { name: '비즈니스', value: 'business' },
        { name: '엔터테인먼트', value: 'entertainment' },
        { name: '건강', value: 'health' },
        { name: '과학', value: 'science' },
        { name: '스포츠', value: 'sports' },
        { name: '기술', value: 'technology' },
    ]

    const [news, setNews] = useState([]) // 뉴스 데이터를 저장할 상태
    const [category, setCategory] = useState('all') // 카테고리를 저장할 상태

    // useEffect를 사용하여 컴포넌트가 마운트될 때, 업데이트될 때, 언마운트될 때 뉴스 데이터를 불러오는 작업을 수행
    useEffect(() => {
        // async 함수를 만들어 useEffect 내부에서 호출
        const fetchData = async () => {
            let response
            try {
                // 카테고리가 'all'이면 모든 뉴스 데이터를 불러옴
                if (category === 'all') {
                    response = await axios.get('https://newsapi.org/v2/top-headlines?country=kr&apiKey=API_KEY')
                } else {
                    // 카테고리가 'all'이 아니면 해당 카테고리의 뉴스 데이터를 불러옴
                    response = await axios.get(
                        `https://newsapi.org/v2/top-headlines?country=kr&category=${category}&apiKey=API_KEY`
                    )
                }
                setNews(response.data.articles) // 뉴스 데이터를 상태에 저장 (articles 배열)
            } catch (error) {
                console.error(error)
            }
        }

        fetchData() // fetchData 함수를 호출
    }, [category]) // 'category' 상태가 변할 때마다 이 useEffect가 실행됩니다.

    // 카테고리 버튼을 클릭하면 'category' 상태를 변경하는 함수
    const selectCategory = (category) => {
        setCategory(category)
    }

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

            {categoryList.map((item, index) => (
                <button key={index} onClick={() => selectCategory(item.value)}>
                    {item.name}
                </button>
            ))}

            <ul>
                {news.map((article, index) => (
                    <li key={index}>
                        <a href={article.url} target="_blank" rel="noreferrer">
                            {article.title}
                        </a>
                    </li>
                ))}
            </ul>
        </div>
    )
}

export default News

fetch 함수와 axios 라이브러리의 차이점은 다음과 같습니다.

  • fetch 함수는 브라우저 기본 내장 함수로, 브라우저 환경에서만 사용할 수 있습니다.
  • axios는 HTTP 클라이언트 라이브러리로, 브라우저와 Node.js 환경에서 모두 사용할 수 있습니다.
  • axios를 사용하면 간단하게 API 호출을 할 수 있으며, Promise 기반의 API를 제공합니다.
  • axios는 요청과 응답을 JSON 형태로 자동 변환해주기 때문에, JSON 데이터를 쉽게 다룰 수 있습니다.
티스토리 친구하기