2024. 11. 4. 16:26ㆍReact
useEffect
와 fetch()
는 React에서 비동기 작업과 데이터 가져오기를 처리하는 데 핵심적인 역할을 합니다. 이 두 가지를 결합하면, 서버에서 데이터를 가져와 컴포넌트에 로드하는 것을 쉽게 구현할 수 있습니다.
1. useEffect
란?
useEffect
는 React 훅 중 하나로, 컴포넌트가 렌더링될 때 특정 동작(사이드 이펙트)을 수행할 수 있게 해줍니다. 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. useEffect
와 fetch()
를 결합하여 데이터 가져오기
React 컴포넌트가 렌더링될 때 서버에서 데이터를 가져와야 하는 경우, useEffect
와 fetch()
를 결합하여 비동기 데이터를 로드할 수 있습니다.
예제: useEffect
와 fetch()
를 사용하여 데이터 가져오기
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;
설명:
- 상태 설정:
data
: 서버에서 가져온 데이터를 저장하는 상태입니다.loading
: 데이터가 로딩 중인지 여부를 나타내는 상태입니다.error
: 데이터 가져오기 오류를 관리하는 상태입니다.
useEffect
와fetch()
:fetch()
로 데이터를 가져오고, JSON으로 변환한 후 성공 시data
상태에 저장합니다.- 오류가 발생하면
error
상태에 오류 메시지를 저장하고, 로딩 상태를false
로 업데이트하여 로딩이 끝났음을 나타냅니다.
- 렌더링 조건:
- 로딩 중일 때는
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. 비동기 fetch
와 async/await
사용
fetch
는 Promise 기반이므로, 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;
코드 설명
- 상태 선언:
data
: 서버에서 가져온 데이터를 저장하는 상태입니다.loading
: 데이터가 로드 중인지 여부를 나타내는 상태입니다.error
: 요청 중 발생한 오류를 관리하는 상태입니다.
useEffect
내부의 비동기 함수:fetchData
함수:async
함수로 정의하여await
로fetch
요청을 처리합니다.- 에러 처리: 요청이 실패하거나 상태 코드가
200
이 아닌 경우 에러 메시지를error
상태에 저장하여 관리합니다. - 로딩 및 에러 초기화:
fetchData
호출 시setLoading(true)
와setError(null)
로 로딩 상태와 에러 상태를 초기화합니다.
- 조건부 렌더링:
- 로딩 중일 때는 "Loading..." 메시지를 표시합니다.
- 에러가 발생했을 때는 오류 메시지를 보여줍니다.
- 데이터가 성공적으로 로드된 경우 JSON 데이터를 화면에 출력합니다.
async/await
를 사용한 fetch의 장점
- 가독성 향상:
then
과catch
체인 없이 코드가 순차적으로 진행되어 더 읽기 쉬운 코드를 작성할 수 있습니다. - 에러 처리 일관성:
try...catch
구문을 통해 에러를 일관되게 처리할 수 있습니다.
이렇게 async/await
와 fetch
를 결합하면 데이터를 직관적이고 명확하게 가져오고 처리할 수 있어, React에서 비동기 요청을 관리하는 데 매우 유용합니다. useEffect
와 결합해 데이터 로딩, 상태 업데이트, 에러 처리 등을 함께 관리할 수 있어 효과적인 비동기 요청 처리 구조를 구축할 수 있습니다.
6. useEffect
와 async/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...
메시지를 표시하고, 로딩이 완료되면 사용자 정보를 출력합니다.
useEffect
와 fetch
는 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 |