✅ [13편] 리액트에서 비동기 처리 완전 정복
프로미스(Promise), async/await, 그리고 실무 패턴까지 리액트에서 API 통신과 같은 비동기 로직은 거의 모든 프로젝트에서
반드시 필요한 핵심 요소입니다.
특히 SPA(Single Page Application)의 구조상, 버튼 클릭 → 데이터 요청 → 화면 업데이트 흐름이 자연스럽게 연결되어야
하고, 그 과정에서 UI가 멈추지 않도록 처리하는 것이 중요합니다.
이 글에서는 Promise와 async/await의 개념, 리액트에서 비동기를 효율적으로 관리하는 패턴, 실무에서 주의해야 할 점,
성능까지 고려한 베스트 프랙티스를 하나씩 자세하게 살펴보겠습니다.
🟦 1. 리액트에서 비동기가 중요한 이유
리액트 컴포넌트는 렌더링을 통해 사용자에게 UI를 보여주는 역할을 합니다.
하지만 실제 서비스에서는 단순히 화면만 보여주는 것이 아니라 다음과 같은 과정이 발생합니다.
API 서버에 데이터 요청
캐싱된 데이터 불러오기
버튼 클릭 후 네트워크 요청
사용자 입력 검증 후 서버 전송
이미지/파일 업로드
이러한 과정 대부분이 즉시 끝나지 않는 작업, 즉 비동기 작업입니다.
만약 이 모든 작업이 ‘동기 방식’으로 실행된다면, 하나의 작업이 끝날 때까지 UI가 멈추게 되고, 사용자 경험은 크게 떨어지게
됩니다.
이런 문제를 바로 해결하는 것이 비동기 처리(Asynchronous)이며, 리액트는 이를 효율적으로 처리하기 위해 다양한 방식과 패턴을 지원합니다.
🟦 2. Promise 기본 개념과 리액트에서의 활용
Promise는 비동기 작업을 표현하는 자바스크립트 객체입니다.
fetch API 역시 Promise 기반으로 동작하므로, 리액트 개발 시 자주 사용됩니다.
✔ Promise 예제
fetch("https://api.example.com/data")
.then(res => res.json())
.then(data => {
console.log(data);
})
.catch(err => {
console.error("API 실패:", err);
});
Promise는 then → then → catch 구조로 연결되며 비동기 작업의 성공/실패를 명확하게 처리할 수 있습니다.
✔ Promise의 장점
비동기 흐름을 제어하기 쉽다
성공/실패 분리 가능
다양한 비동기 작업을 조합할 수 있다
하지만 then 체인이 길어지면 가독성이 떨어지고, 중첩이 많아지면 코드 유지보수가 어려워지는 단점이 있습니다.
이 점을 해결하기 위해 등장한 문법이 바로 async/await입니다.
🟦 3. async/await로 더 직관적이고 간결한 비동기 처리
async/await는 Promise를 기반으로 하지만, 동기 방식처럼 코드를 작성할 수 있어 가독성이 뛰어납니다.
✔ async/await 예시
const getData = async () => {
try {
const res = await fetch("https://api.example.com/data");
const data = await res.json();
console.log(data);
} catch (error) {
console.error("에러 발생:", error);
}
};
✔ async/await 장점
코드 흐름이 자연스럽고 읽기 쉬움
try/catch로 에러 처리 간단
중첩된 콜백 문제 해결
이 때문에 실무에서는 거의 모든 비동기 로직을 async/await로 작성합니다.
🟦 4. 리액트에서 API 요청은 보통 useEffect에서 수행한다
리액트 컴포넌트는 렌더링 후 필요한 데이터를 가져오기 위해 API 요청을 수행합니다.
이때 가장 많이 사용되는 방식이 useEffect입니다.
하지만 useEffect는 async 함수를 직접적으로 지정할 수 없습니다.
왜냐하면 useEffect의 반환값은 클린업 함수여야 하며, async 함수는 Promise를 반환하기 때문입니다.
✔ 올바른 패턴
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch("/api/data");
const data = await res.json();
setItems(data);
} catch (e) {
console.error(e);
}
};
fetchData();
}, []);
이 구조는 다음과 같은 장점이 있습니다.
렌더링 후 한 번만 실행
async/await 사용 가능
명확한 에러 처리 가능
메모리 누수 방지 코드 추가 가능
🟦 5. 언마운트된 컴포넌트에서 setState를 호출하지 않도록 주의
리액트 신규 개발자들이 가장 많이 겪는 에러 중 하나가 바로 다음입니다.
“Can't perform a React state update on an unmounted component”
이는 API 요청이 끝나기 전에 컴포넌트가 언마운트되면, 완료된 응답이 setState를 호출하면서 발생합니다.
이를 방지하는 방법은 여러 가지가 있습니다.
✔ 방법 1: AbortController로 요청 취소하기
useEffect(() => {
const controller = new AbortController();
const getData = async () => {
try {
const res = await fetch("/api/data", { signal: controller.signal });
const data = await res.json();
setData(data);
} catch (err) {
if (err.name === "AbortError") return;
}
};
getData();
return () => controller.abort();
}, []);
AbortController는 fetch 요청을 강제로 취소할 수 있어 안전합니다.
✔ 방법 2: isMounted 플래그 활용
useEffect(() => {
let isMounted = true;
const load = async () => {
const res = await fetch("/api");
const data = await res.json();
if (isMounted) setData(data);
};
load();
return () => (isMounted = false);
}, []);
컴포넌트가 언마운트되면 더는 setState가 호출되지 않도록 막아줍니다.
🟦 6. 로딩 상태와 에러 상태를 함께 관리해야 한다
비동기 요청에서는 성공만 처리하는 것이 아니라 다음 상태도 함께 관리해야 합니다.
로딩 중인지
성공했는지
실패했는지
✔ 실무에서 자주 사용하는 패턴
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const res = await fetch("/api/data");
const data = await res.json();
setItems(data);
} catch (e) {
setError("데이터를 불러오지 못했습니다.");
} finally {
setLoading(false);
}
};
fetchData();
}, []);
이 패턴은 다음과 같은 특징이 있습니다.
요청 시작/종료 시 로딩 상태 업데이트
에러 발생 시 사용자에게 안내
응답 상태에 따라 UI를 조건부 렌더링 가능
🟦 7. async/await + try/catch + finally = 안정적인 비동기 처리 구조
API 요청은 항상 성공한다고 가정할 수 없습니다.
네트워크 문제, 서버 오류, 사용자 취소 등 다양한 실패 케이스가 존재합니다.
따라서 async/await와 함께 반드시 다음 패턴을 유지해야 합니다.
✔ try → 성공 시 처리
✔ catch → 실패 시 처리
✔ finally → 로딩 종료, cleanup 등
이 구조는 실무에서 가장 신뢰할 수 있는 구조입니다.
🟦 8. SWR, React Query 같은 데이터 관리 라이브러리를 고려하자
리액트에서 비동기 처리를 직접 구현하는 것은 가능하지만, 규모가 커질수록 복잡성이 증가합니다.
그래서 많은 기업들이 SWR, React Query(TanStack Query) 같은 라이브러리를 사용합니다.
이들 라이브러리는 다음 기능을 자동으로 지원합니다.
캐싱
중복 요청 방지
오류 자동 처리
자동 재요청(revalidate)
로딩 상태 관리
서버 상태와 클라이언트 상태 분리
복잡한 API 호출이 많은 대규모 프로젝트라면 적극 추천되는 방식입니다.
🟦 정리: 리액트 비동기 처리 핵심 포인트
Promise와 async/await는 비동기 처리의 기본
useEffect 안에서 async 직접 사용하면 안 됨
언마운트 후 setState 호출 방지 필요
로딩/에러/완료 상태를 명확히 관리해야 함
필요하면 SWR, React Query 등 라이브러리 사용
try/catch로 안정적인 에러 처리를 구성
비동기 처리는 단순히 API를 호출하는 것이 아니라, 데이터 흐름을 안정적으로 관리하고 UI 경험을 높이는 핵심 요소입니다.
꾸준히 패턴을 익히면 리액트 개발 생산성이 크게 향상될 것입니다.
'리액트' 카테고리의 다른 글
| ✅ [17편] 리액트 배포 방법 완전 정복: Vercel, Netlify, GitHub Pages 3가지 플랫폼 비교와 실무 활용 가이드 (0) | 2025.11.26 |
|---|---|
| ✅ [16편] 리액트 성능 측정 도구 완벽 가이드: React DevTools, Lighthouse 활용법과 실무 최적화 전략 (0) | 2025.11.26 |
| ✅ [15편] Next.js와 리액트의 차이점 완전 정리: 구조·기능·성능·실무 선택 기준까지 (0) | 2025.11.26 |
| ✅ [14편] 리액트 서버 컴포넌트와 클라이언트 컴포넌트 완전 정복: 개념, 차이점, 구조, 실무 적용 전략까지 (0) | 2025.11.26 |
| ✅ 12편. 리액트에서 CSS 스타일링 방법 총정리 (Styled-Components, CSS Modules 등) (0) | 2025.11.26 |
| ✅ 11편. 리액트 프로젝트 폴더 구조 설계 Best Practice (0) | 2025.11.26 |
| ⭐ 10편 — 리액트에서 비동기 처리 완전 정리: fetch, axios, async/await, useEffect 패턴까지 실전 가이드 (0) | 2025.11.25 |
| ⭐ 9편 — 리액트 상태 관리 기본기: Context API vs Redux 실전 비교, 초보자도 알 수 있게 정리 (0) | 2025.11.25 |