본문 바로가기

리액트

⭐ 10편 — 리액트에서 비동기 처리 완전 정리: fetch, axios, async/await, useEffect 패턴까지 실전 가이드

⭐ 10편 — 리액트에서 비동기 처리 완전 정리: fetch, axios, async/await, useEffect 패턴까지 실전 가이드

리액트로 API를 호출하다 보면
다음과 같은 문제들을 한 번쯤 겪게 됩니다.
useEffect 안에서 async 쓰면 왜 오류 날까?
fetch와 axios는 뭐가 다를까?
상태 업데이트 시점이 꼬이는 이유는?
API를 여러 번 호출되는 문제는 왜 생길까?
이번 글에서는 비동기 처리의 원리부터
실무 패턴, 문제 해결법까지 차근차근 정리해드릴게요.


✅ 1. 리액트에서 비동기를 다루는 기본 개념

리액트는 렌더링을 먼저 실행하고,
그 후에 비동기 코드를 처리합니다.
즉, API 호출은 반드시 렌더링 이후에 실행되며
이때 사용하는 대표적인 도구가 바로 useEffect입니다.


✅ 2. fetch 기본 사용법

fetch는 브라우저에 내장된 HTTP 요청 API입니다.
✔ GET 요청 예제
fetch("https://api.example.com/users")
.then(res => res.json())
.then(data => console.log(data));
✔ async/await 버전
async function getUsers() {
const res = await fetch("https://api.example.com/users");
const data = await res.json();
console.log(data);
}

🔥 axios가 더 많이 쓰이는 이유

axios는 fetch보다 다음 장점이 있어서 실무에서 더 자주 사용됩니다.
✔ JSON 변환 자동 처리
✔ 인터셉터로 요청/응답 가로채기 가능
✔ 에러 처리 구조가 더 명확
✔ 설정이 깔끔
✔ 환경별 기본 설정(baseURL) 지원
✔ axios 사용 예제
import axios from "axios";
const res = await axios.get("/users");
console.log(res.data);


⭐ fetch vs axios 비교

특징 fetch axios
기본 제공 여부 브라우저 내장 설치 필요
JSON 파싱 직접 .json() 호출 자동 파싱
인터셉터 없음 있음
에러 처리 번거로움 단순
파일 업로드 약간 복잡 쉬움
✔ 결론: 규모가 있는 프로젝트는 axios 추천, 단순한 작업은 fetch도 충분합니다


✅ 3. useEffect 안에서 비동기 처리하는 올바른 방식

많은 초보자들이 다음처럼 잘못 작성합니다.
❌ 잘못된 코드
useEffect(async () => {
const res = await axios.get("/users");
}, []);
useEffect는 동기 함수여야 하기 때문에
async를 직접 넣으면 경고가 발생합니다.
✔ 올바른 패턴 1: 내부에 async 함수 선언 후 호출
useEffect(() => {
async function fetchData() {
const res = await axios.get("/users");
setUsers(res.data);
}
fetchData();
}, []);
이 방식이 가장 기본적이고 안정적입니다.
✔ 올바른 패턴 2: 즉시 실행 함수(IIFE) 사용
useEffect(() => {
(async () => {
const res = await axios.get("/users");
setUsers(res.data);
})();
}, []);
깔끔한 패턴이라 실무에서도 많이 사용합니다.


🔥 useEffect가 두 번 실행되는 이유 (중요!)

React.StrictMode가 활성화된 개발 환경에서는
useEffect가 두 번 호출되는 게 정상입니다.
이는 리액트가 “이 코드가 안전하게 다시 렌더링될 수 있는지” 체크하기 위함입니다.
이를 해결하는 방법은:
✔ StrictMode를 제거하거나
✔ 상태 관리 로직을 안정적으로 구성하는 것입니다.
실제 배포 환경에서 useEffect는 한 번만 실행됩니다.


🔥 API가 여러 번 호출될 때 해결법


✔ 1) useEffect 의존성 배열을 정리
useEffect(() => {
fetchData();
}, []); // 빈 배열 → 딱 한 번만 실행
✔ 2) 상태를 useEffect 안에서 직접 수정하지 않기
잘못된 코드 예:
useEffect(() => {
setCount(count + 1);
});
이런 패턴은 무한 렌더링을 일으킵니다.
✔ 3) React Query 사용 고려
실무에서는 state로 API 결과를 직접 관리하지 않고
React Query로 데이터 캐싱 + 비동기 로직을 관리합니다.
하지만 초급자는 일단 useEffect 패턴부터 익히면 충분합니다.


⭐ 비동기 처리에서 자주 발생하는 문제 & 해결법

❌ 문제 1) 데이터 로딩 시 깜빡임 발생
✔ 해결: 로딩 상태 추가
const [loading, setLoading] = useState(true);
useEffect(() => {
async function load() {
const res = await axios.get("/posts");
setPosts(res.data);
setLoading(false);
}
load();
}, []);
❌ 문제 2) 응답 전에 컴포넌트가 언마운트되는 경우 오류 발생
✔ 해결: cleanup으로 방어
useEffect(() => {
let isMounted = true;
(async () => {
const res = await axios.get("/posts");
if (isMounted) setPosts(res.data);
})();
return () => {
isMounted = false;
};
}, []);
❌ 문제 3) 비동기 로직이 컴포넌트마다 분산되어 관리 어려움
✔ 해결: API 함수를 외부로 분리
// api.js
export const getUsers = () => axios.get("/users");
// 컴포넌트
useEffect(() => {
getUsers().then(res => setUsers(res.data));
}, []);
실무에서는 거의 필수 패턴입니다.


✨ 마무리: 비동기 처리는 리액트의 핵심 중 핵심

리액트가 SPA 방식으로 동작하는 이상
비동기 처리는 필수적으로 따라오는 기능입니다.
이번 글에서 다룬 내용은
✔ fetch/axios 차이
✔ async/await 올바른 사용법
✔ useEffect 안에서의 비동기 처리 패턴
✔ API 중복 호출 방지
✔ 실무 문제 해결법