React에서 비동기 처리 최적화하기

2025. 10. 25. 14:10·Frontend/React

리액트(React)에서 시간이 오래 걸리는 작업은 대부분 비동기 처리(asynchronous processing)와 관련된다. 리액트는 UI를 빠르게 반응시키는 것이 핵심이기 때문에, 무거운 작업이 메인 스레드를(blocking) 막지 않도록 비동기 처리나 별도 스레드로 분리해야 한다.

 

1. 비동기로 처리하는 이유

리액트는 기본적으로 싱글 스레드(Single Thread) 환경에서 동작한다. 즉, UI 렌더링과 자바스크립트 실행이 같은 스레드에서 처리된다. 따라서, 아래 작업이 동기적으로 실행되면 화면이 멈추거나 클릭이 먹지 않는 UI 프리징(freezing) 현상이 발생한다.

  • 무거운 연산: 대용량 데이터 계산, 복잡한 정렬 등
  • 네트워크 요청: API 호출
  • 파일 입출력: 이미지 업로드 등

그래서 이런 작업은 비동기 처리 또는 별도 스레드(Web Worker)로 분리해야 한다.

 

2. 대표적인 비동기 처리 방식

목적 방법
API 요청 fetch, axios, async/await
지연 실행 / 애니메이션 setTimeout, setInterval, requestAnimationFrame
복잡한 계산 분리 Web Worker
비동기 이벤트 Promise, async 함수

 

3. 비동기 처리 예시코드로 이해하기

 

3-1. useEffect + async/await (API 요청)

비동기 API 호출이나 타이머 작업은 보통 useEffect 안에서 처리한다. cleanup과 에러 처리까지 포함하면 아래와 같이 구성할 수 있다.

import { useEffect, useState } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);
  const [error, setError] = useState(null);

  useEffect(() => {
    let isMounted = true;

    const fetchUsers = async () => {
      try {
        const res = await fetch('/api/users');
        if (!res.ok) throw new Error('API 요청 실패');
        const data = await res.json();
        if (isMounted) setUsers(data);
      } catch (err) {
        if (isMounted) setError(err.message);
      }
    };

    fetchUsers();

    return () => { isMounted = false; };
  }, []);

  if (error) return <div>{error}</div>;
  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

 

3-2. setTimeout, setInterval, requestAnimationFrame (지연 실행 / 애니메이션)

UI 애니메이션이나 반복 처리 작업은 비동기 타이머를 사용한다.

import { useEffect, useState } from 'react';

function Countdown() {
  const [count, setCount] = useState(10);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(prev => prev - 1);
    }, 1000);

    return () => clearInterval(id);
  }, []);

  return <div>카운트다운: {count}</div>;
}

 

requestAnimationFrame은 애니메이션 프레임 단위로 UI를 갱신할 때 사용한다.

function animateBox() {
  let pos = 0;
  function step() {
    pos += 2;
    document.getElementById('box').style.left = pos + 'px';
    if (pos < 300) requestAnimationFrame(step);
  }
  requestAnimationFrame(step);
}

 

3-3. Web Worker (무거운 계산 분리)

CPU 연산이 많은 작업은 Web Worker로 분리한다. 리액트에서는 worker와 상태 업데이트를 연결해 사용할 수 있다.

// worker.js
self.onmessage = (e) => {
  const result = heavyCompute(e.data);
  self.postMessage(result);
};

// React Component
import { useEffect, useState } from 'react';

function HeavyTask({ input }) {
  const [result, setResult] = useState(null);

  useEffect(() => {
    const worker = new Worker(new URL('./worker.js', import.meta.url));
    worker.postMessage(input);

    worker.onmessage = (e) => setResult(e.data);

    return () => worker.terminate();
  }, [input]);

  return <div>결과: {result}</div>;
}

3-4. Promise / async 이벤트 처리

사용자 인터랙션에 따른 비동기 작업도 처리한다.

function SaveButton() {
  const handleClick = async () => {
    try {
      await saveDataToServer();
      alert('저장 완료!');
    } catch (err) {
      alert('저장 실패: ' + err.message);
    }
  };

  return <button onClick={handleClick}>저장</button>;
}

3-5. Suspense + React.lazy (비동기 컴포넌트)

React 18 이상에서는 비동기 컴포넌트를 UI 흐름에 통합할 수 있다.

import React, { Suspense } from 'react';

const Profile = React.lazy(() => import('./Profile'));

function App() {
  return (
    <Suspense fallback={<div>로딩 중...</div>}>
      <Profile />
    </Suspense>
  );
}

 

Suspense는 로딩 중 상태를 표현하고, 컴포넌트 렌더링이 완료되면 자동으로 교체한다.

3-6. useTransition (UI 렌더링 우선순위 분리)

렌더링이 오래 걸리는 작업은 useTransition을 사용해 우선순위를 낮출 수 있다.

import { useTransition, useState } from 'react';

function SearchList({ items }) {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    const value = e.target.value;
    startTransition(() => {
      setQuery(value);
    });
  };

  const filtered = items.filter(item => item.includes(query));

  return (
    <>
      <input onChange={handleChange} />
      {isPending ? <div>로딩 중...</div> : <ul>{filtered.map(i => <li key={i}>{i}</li>)}</ul>}
    </>
  );
}

 

사용자가 입력한 값은 즉시 반영하고, 리스트 필터링 렌더링은 백그라운드에서 비동기 처리된다.

 

4. React 비동기 처리 요약

리액트는 싱글 스레드 환경에서 동작하기 때문에, 오래 걸리는 연산이나 네트워크 요청은 비동기 처리 또는 별도 스레드 분리가 필수적이다.

상황별로 적용할 수 있는 대표적인 비동기 처리 및 최적화 방법을 요약하면 아래와 같다.

상황 해결 방법 사용 예시 / 주요 기술
API 요청 async/await, fetch, axios useEffect 안에서 비동기 데이터 호출 및 상태 업데이트
UI 애니메이션 / 지연 처리 setTimeout, setInterval, requestAnimationFrame 애니메이션, 카운트다운, 일정 주기 업데이트 등
CPU가 무거운 계산 Web Worker 이미지 압축, 복잡한 수학 연산, 대규모 데이터 가공
사용자 이벤트 비동기 처리 Promise, async 함수 버튼 클릭 후 API 호출, 저장/검증 로직 처리
비동기 컴포넌트 로딩 React.lazy, Suspense 동적 import, 코드 분할(Code Splitting)
렌더링이 느린 경우 useTransition, memo, useMemo 대규모 리스트 렌더링, 입력 지연 최소화
반복 렌더링 최적화 React.memo, useCallback, useMemo 동일한 props로 불필요한 재렌더링 방지
  • 비동기 처리의 핵심 목적은 “UI 멈춤 방지”와 “렌더링 성능 향상”이다.
  • I/O 지연(API, 파일, 애니메이션)은 async/await 기반의 비동기 처리로, CPU 연산은 Web Worker로 분리한다.
  • 렌더링 최적화는 React의 Concurrent Feature(useTransition, Suspense)와 메모이제이션(useMemo, React.memo)을 함께 사용하는 것이 효과적이다.

'Frontend > React' 카테고리의 다른 글

useEffect vs useLayoutEffect  (0) 2025.10.27
상태(stateful) & 비상태(stateless) 컴포넌트  (0) 2025.10.25
useRef 자세히 알아보기  (0) 2025.10.24
useEffect의 cleanup 함수 알아보기  (0) 2025.10.24
React 메모이제이션과 최적화  (0) 2025.10.24
'Frontend/React' 카테고리의 다른 글
  • useEffect vs useLayoutEffect
  • 상태(stateful) & 비상태(stateless) 컴포넌트
  • useRef 자세히 알아보기
  • useEffect의 cleanup 함수 알아보기
JTB
JTB
웹/앱 개발 정보를 공유하고 있습니다.
  • JTB
    JTechBlog
    JTB
  • 전체
    오늘
    어제
    • All About Programming;)
      • Computer Science
        • Terminology and Concepts
        • Network
        • Operating System
        • Database
        • Data Structure
        • Web Development
      • Frontend
        • Javascript Essentials
        • Perfomance Optimization
        • JS Patterns
        • React
        • Next.js
        • Flutter
        • Testing
      • Backend
        • Node.js
      • DevOps
        • Docker & Kubernetes
      • Coding Test
        • LeetCode
        • Programmers
      • Tech Books & Lectures
        • Javascript_Modern JS Deep d..
        • Network_IT 엔지니어를 위한 네트워크 입문
      • Projects
        • PolyLingo_2025
        • Build Your Body_2024
        • JStargram_2021
        • Covid19 Tracker_2021
        • JPortfolio_2021
      • BootCamp_Codestates
        • TIL
        • TILookCloser
        • Pre Tech Blog
        • IM Tech Blog
        • Daily Issues and DeBugging
        • First Project
        • Final Project
        • Sprint Review
        • Good to Know
        • Socrative Review
        • HTML &amp; CSS
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 글쓰기
    • 관리
  • 공지사항

  • 인기 글

  • 태그

    스코프
    need a database
    How memory manage data
    CPU scheduling algorithm
    VoiceJournal
    Threads and Multithreading
    Binary Tree BFS
    Operating System
    딥다이브
    leetcode
    structure of os
    Shared resources
    TCP/IP
    프론트엔드 성능 최적화 가이드
    Data Structure
    js pattern
    자바스크립트 딥다이브
    커리어
    자바스크립트
    이벤트
    mobile app
    모던 자바스크립트 Deep Dive
    Javascript Essentials
    database
    DOM
    Time complexity and Space complexity
    indie hacker
    testing
    polylingo
    Network
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
JTB
React에서 비동기 처리 최적화하기
상단으로

티스토리툴바