리액트(React)는 UI 구성을 위한 가장 인기 있는 라이브러리 중 하나로서, 현대 웹 애플리케이션의 프론트엔드 개발에 광범위하게 사용되고 있습니다. 리액트의 핵심 개념 중 하나는 컴포넌트입니다. 컴포넌트는 독립적으로 관리되는 UI의 일부분으로, 각각의 컴포넌트는 상태(state)와 생명주기(lifecycle)를 가집니다. 이 글에서는 리액트의 useEffect
훅에 대해 자세히 설명하고, 컴포넌트의 생명주기와 어떻게 비동기 작업을 관리할 수 있는지에 대해 살펴보겠습니다.
1. 컴포넌트의 생명주기(lifecycle)
리액트 컴포넌트의 생명주기는 크게 세 가지 단계로 나눌 수 있습니다: 마운트(mount), 업데이트(update), 언마운트(unmount)입니다. 각 단계에서는 컴포넌트가 상태 변화나 부모 컴포넌트의 변화에 따라 어떻게 행동하는지를 정의할 수 있습니다.
- 마운트(Mount): 컴포넌트가 DOM에 처음 렌더링될 때 발생합니다. 이 단계에서 주로 초기 상태를 설정하거나 API를 호출하는 등의 작업을 수행합니다.
- 업데이트(Update): 컴포넌트의 상태나 props가 변화할 때 발생합니다. 상태 변화에 따라 UI를 재렌더링하거나 관련 작업을 수행할 수 있습니다.
- 언마운트(Unmount): 컴포넌트가 DOM에서 제거될 때 발생합니다. 이 단계에서 클린업(cleanup) 작업을 수행할 수 있습니다.
2. useEffect 훅의 역할
useEffect
훅은 컴포넌트의 생명주기 메서드를 대체하는 데 사용됩니다. 이 훅은 컴포넌트가 마운트되거나 업데이트 될 때 특정 작업을 수행하도록 정의할 수 있습니다. useEffect
는 다음과 같은 용도로 사용됩니다:
- 데이터 fetching (API 호출)
- 구독(subscription)
- DOM 업데이트
- 타이머 설정 및 클린업
3. useEffect 사용 방법
useEffect
훅은 다음과 같이 사용됩니다:
import React, { useEffect, useState } from 'react';
const MyComponent = () => {
const [data, setData] = useState(null);
useEffect(() => {
// API 호출
fetch('https://api.example.com/data')
.then(response => response.json())
.then(json => setData(json));
// 클린업 함수 리턴
return () => {
// 필요시 클린업 작업 수행
};
}, []); // 의존성 배열
return (
{data ? {data.title}
: Loading...
}
);
};
위의 예제에서 useEffect
는 컴포넌트가 처음 마운트될 때 API를 호출하여 데이터를 가져오는 역할을 합니다. 의존성 배열이 빈 배열([]
)로 설정되어 있기 때문에, 이 effect는 컴포넌트가 최초로 마운트될 때 단 한 번만 실행됩니다. 데이터 fetching이 완료되면 setData
를 호출하여 상태를 업데이트하고, 컴포넌트가 다시 렌더링됩니다.
4. 비동기 작업과 useEffect
리액트에서는 비동기 작업을 할 때 주의해야 할 점이 있습니다. 예를 들어, 컴포넌트가 언마운트된 후에도 비동기 작업이 완료되면 상태를 업데이트하려고 할 경우 오류가 발생할 수 있습니다. 이를 방지하려면 효과의 클린업 기능을 활용해야 합니다.
클린업 함수는 useEffect
가 반환하는 함수로, 컴포넌트가 언마운트되거나 의존성 배열의 값이 변경될 때 호출됩니다.
5. 예제: 비동기 데이터 fetching과 오류 처리
import React, { useEffect, useState } from 'react'; const DataFetchingComponent = () => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { const response = await fetch('https://api.example.com/data'); if (!response.ok) { throw new Error('Network response was not ok'); } const jsonData = await response.json(); setData(jsonData); } catch (error) { setError(error); } finally { setLoading(false); } }; fetchData(); return () => { // 클린업 함수 setData(null); setError(null); }; }, []); if (loading) return
Loading...
; if (error) return{error.message}
; return (Data:
{JSON.stringify(data, null, 2)});
};
위의 예제에서는
fetchData
라는 비동기 함수를 정의하고, 이를useEffect
내에서 호출합니다. 에러 처리를 위해setError
를 사용하여 API 호출 중 발생할 수 있는 네트워크 오류를 포착합니다. 로딩 상태는loading
변수를 통해 관리하고 있습니다. 클린업 함수로는 상태 초기화를 수행하여, 컴포넌트가 언마운트되었을 때 상태를 리셋합니다.6. useEffect의 의존성 배열
useEffect
훅의 두 번째 인자는 의존성 배열입니다. 이 배열은 효과가 재실행될 조건을 설정하는데 사용됩니다. 의존성 배열에 포함된 변수가 변경되면, 해당 effect가 다시 실행됩니다.useEffect(() => { // effect 내용 }, [variable1, variable2]);
위와 같은 형식으로 설정하면,
variable1
또는variable2
가 변화할 때마다 effect가 재실행됩니다. 아래에 의존성 배열의 몇 가지 예를 보여드리겠습니다.예제 1: 상태 변화에 따라 effect 재실행
const TimerComponent = () => { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { setCount(prevCount => prevCount + 1); }, 1000); return () => clearInterval(timer); }, []); return
Count: {count}
; };위의 코드에서
setInterval
을 통해 1초마다 카운트를 증가시키고 있으며, 의존성 배열이 빈 배열이기 때문에 컴포넌트가 처음 렌더링 될 때만 타이머가 설정됩니다.예제 2: props 변화에 따른 effect 재실행
const UserProfile = ({ userId }) => { const [userData, setUserData] = useState(null); useEffect(() => { const fetchUserData = async () => { const response = await fetch(`https://api.example.com/users/${userId}`); const data = await response.json(); setUserData(data); }; fetchUserData(); }, [userId]); // userId가 변할 때마다 재실행 return
{userData ? userData.name : 'Loading...'}; };여기서
userId
가 변할 때마다 사용자 데이터가 다시 fetch됩니다. 이는 컴포넌트가 부모 컴포넌트에서 전달받은 props에 따라 렌더링되는 경우 매우 유용합니다.7. 여러 개의 useEffect 훅 사용하기
리액트에서는 하나의 컴포넌트에서 여러 개의
useEffect
훅을 사용할 수 있습니다. 각 effect는 독립적으로 작동하며, 서로의 의존성에 영향을 미치지 않습니다.const MultipleEffectsComponent = () => { const [value, setValue] = useState(0); const [name, setName] = useState(''); // value 의 변화에 따른 effect useEffect(() => { console.log('Value changed:', value); }, [value]); // name 의 변화에 따른 effect useEffect(() => { console.log('Name changed:', name); }, [name]); return (
setValue(Number(e.target.value))} /> setName(e.target.value)} />); };위의 예제에서
value
와name
두 개의 상태가 각각의useEffect
훅에 의해 감지되고, 변화가 발생할 때마다 해당 로그가 출력됩니다.8. 결론
useEffect
훅은 리액트에서 상태 관리와 비동기 작업을 보다 쉽게 수행할 수 있도록 해줍니다. 컴포넌트의 생명주기를 이해하고, 적절한 타이밍에 효과를 설정하는 것이 매우 중요합니다. 또한, 클린업 함수를 통해 컴포넌트가 언마운트될 때 발생할 수 있는 웹 개발에서의 일반적인 문제를 방지할 수 있습니다. 이 글에서는useEffect
의 사용법과 예제를 중심으로 설명했으며, 이를 통해 비동기 작업과 상태 관리를 효율적으로 구현하는 방법을 배웠습니다. 리액트의 생명주기 관리와useEffect
의 활용이 여러분의 프론트엔드 개발을 더욱 풍부하고 강력하게 만들어줄 것입니다.9. 추가 학습 자료