React 학습을 통해 프로젝트를 만들려고 합니다.
학습을 하며 useEffect()를 이해하는데 큰 어려움이 있었습니다.
기록을 통해 학습한 내용을 잊지 않기 위해 작성합니다.
useEffect()
STEP 1. "useEffect는 Side effect를 분리하기 위해 사용한다."
useEffect는 Side effect를 작업을 분리하기 위해 사용합니다. Side effect는 컴포넌트의 렌더링 과정 외부에서 발생하는 작업입니다. 대표적으로 API 호출(데이터를 가져오거나 보내는 작업), DOM(React 외부에서 DOM을 직접 변경하는 작업)
, 타이머 작업(setInterval, setTimeout을 사용하는 작업)
, 구독 설정 (이벤트 리스너를 등록하거나 소켓 연결 설정)
이 있습니다.
STEP 1-2. Side effect를 분리하는 이유
React 컴포넌트 렌더링 원칙을 이유로 합니다. React는 컴포넌트 렌더링을 순수 함수처럼 처리하려고 합니다.
순수 함수란, 같은 입력에 대해 항상 같은 결과를 반환하는 함수입니다. 렌더링은 UI를 만드는 작업에만 집중해야 하며, 외부 상태나 작업과 상호작용 하지 않아야 React의 성능 최적화와 일관된 동작을 유지할 수 있습니다.
렌더링과 Side effect를 분리하지 않았을 때 생기는 문제는 아래와 같습니다.
무한 렌더링 루프 발생
function Example() { const [data, setData] = useState(null); // 데이터 가져오기 코드를 직접 실행 const fetchData = async () => { const response = await fetch("https://jsonplaceholder.typicode.com/posts/1"); const result = await response.json(); setData(result); // 상태 업데이트 }; fetchData(); // 상태 업데이트 → 다시 렌더링 → fetchData 호출 → 무한 루프 발생 return <div>{data ? data.title : "Loading..."}</div>; }
React의 컴포넌트는 상태(State)가 업데이트될 때마다 다시 렌더링됩니다.
만약useEffect
를 사용하지 않고 직접 데이터 가져오기 코드를 작성하면, 상태 업데이트로 인해 컴포넌트가 계속 렌더링되고, 다시 데이터 가져오기 코드가 실행되면서 무한 루프가 발생할 수 있습니다.또한 컴포넌트가 언마운트된 후에도 비동기 작업이 완료되면서 상태를 업데이트하려는 문제가 발생하여 메모리 누수 경고를 출력하는 문제를 야기하기도 합니다.
데이터 요청이 반복적으로 발생
useEffect
를 사용하면 데이터 요청을 컴포넌트가 처음 마운트되었을 때 한 번만 실행되도록 제어할 수 있습니다. 하지만useEffect
를 사용하지 않으면 컴포넌트가 렌더링될 때마다 불필요한 중복 요청이 발생할 수 있습니다. 이로 발생하는 문제점은 같은 데이터를 반복적으로 요청하게 되어 네트워크 성능이 저하, 서버에 과도한 부하가 발생할 수 있습니다.
STEP 2. useEffect()사용방법
useEffect()는 기본적으로 이미지처럼 사용합니다.
첫 번째 인자에는 이팩트 함수가 들어가고 두 번째 인자로는 의존성 배열이 들어갑니다.
의존성 배열은 말 그대로, effect가 의존하고 있는 배열인데 의존하고 있는 배열의 값이 하나라도 변경이 되었을 때 effect함수가 실행됩니다.
만약, effect함수를 mount와 unmount시에 실행하게 하고 싶으면 의존성 배열에 빈 배열을 넣으면 됩니다.
의존성 배열은 생략할 수도 있는데 생략하면 컴포넌트가 업데이트 될 때마다 호출됩니다. React는 처음 렌더링을 업데이트의 한 종류로 간주하기 때문에 처음 렌더링할 때도 호출이 되어짐을 기억합니다.
STEP 3. useEffect() 예시 코드
- 서버 데이터 요청
import React, { useState, useEffect } from "react";
function Example() {
const [data, setData] = useState(null);
useEffect(() => {
const controller = new AbortController(); // 요청 취소용 컨트롤러 생성
const fetchData = async () => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/1", {
signal: controller.signal,
});
const result = await response.json();
setData(result); // 상태 업데이트
} catch (error) {
if (error.name === "AbortError") {
console.log("요청이 취소되었습니다."); // 요청 취소 처리
} else {
console.error("Error:", error);
}
}
};
fetchData();
return () => controller.abort(); // Cleanup: 요청 취소
}, []); // 첫 마운트, 언마운트시 실행
return <div>{data ? data.title : "Loading..."}</div>;
}
export default Example;
AbortController
AbortController
는 브라우저의 비동기 작업을 제어하고 중단할 수 있도록 설계된 API입니다.
이를 통해fetch
요청이나 다른 비동기 작업을 취소할 수 있습니다.controller.abort()
: 비동기 작업을 중단controller.signal
: 작업과 연결되어 중단 신호를 수신.
fetch
와AbortController
의 연결fetch
함수는 옵션으로signal
을 받을 수 있습니다.
이signal
은AbortController
와 연결되어 있으며,controller.abort()
를 호출하면fetch
는 중단됩니다. 동작과정은 아래와 같습니다.
동작과정
AbortController
생성비동기 작업을 중단할 수 있는 컨트롤러 객체를 생성
const controller = new AbortController();
signal
을fetch
요청에 전달controller.signal을 fetch 함수의 옵션으로 전달하여 작업을 연결합니다.
fetch("https://example.com/api", {signal: controller.signal})
abort()
호출controller.abort()를 호출하면 fetch 요청은 중단되며,
AbortError
가 발생합니다.controller.abort();
요청 취소와
AbortError
controller.abort()
를 호출하면 비동기 작업이 중단되고, AbortError가 발생합니다.- 비동기 작업중단
- fetch 요청이 취소되고, 브라우저는 서버로부터의 응답을 더 이상 처리하지 않습니다.
- 단, 서버는 요청이 이미 전송된 경우 응답을 반환할 수 있지만, 클라이언트(브라우저)는 이를 무시합니다.
- AbortError 발생
- 취소된 fetch 요청은 Rejected Promise를 반환하며, 에러 이름은 AbortError입니다.
- 비동기 작업중단
'Framework && Library > React' 카테고리의 다른 글
[React] Context API 사용이유 및 용도, 유의사항, 예시코드 (0) | 2025.01.07 |
---|---|
[React] List 및 key의 개념, map()함수, key의 사용목적은 렌더링 효율에 있다. (0) | 2025.01.05 |
[React] 클래스형 컴포넌트의 bind() + 대안 (0) | 2025.01.04 |