[React] Hooks (2) - useEffect

invisibleVoice·2025년 1월 31일

리액트

목록 보기
9/14
post-thumbnail

useEffect

useEffect는 리액트 컴포넌트가 랜더링 된 이후마다 특정 작업을 수행하도록 설정할 수 있는 훅이다. 어떤 컴포넌트가 화면에 렌더링(=마운트 mount)되었을 때 또는 화면에서 사라졌을 때(=언마운트 unmount) 무언가를 실행하고자 한다면 이것을 사용할 수 있다.

보통 외부 시스템에 연결하고 동기화할 때 사용한다. 외부 시스템이란 네트워크, 브라우저 DOM, 애니메이션, React가 아닌 코드, 브라우저 저장소 등이 포함된다.

useEffect의 구조

import React, { useEffect } from "react";

const App = () => {
    // 렌더링 시 useEffect는 꼭 한번은 실행된다. 콜백이 실행되느냐는 또 다른 이야기.
	useEffect(()=>{
		// 화면에 컴포넌트가 렌더링 된 이후 실행되는 영역. 
      
        // 클린 업 (clean up) = 정리 함수
		return ()=>{
			// 상태가 업데이트되어 리렌더링 되는 순간, 또는 컴포넌트가 언마운트 되는 순간 실행되는 영역
            // Effect가 다시 실행되기 전 이전 Effect를 마무리하는 단계
		}
	}, [의존성_배열])

	return <div>hello react!</div>
};

export default App;

의존성 배열과 클린 업

의존성 배열은 useEffect 훅의 동작 트리거 역할을 한다. 의존성 배열에 값을 넣으면, 그 값이 업데이트 될 때만 useEffect를 실행한다.

clean up함수는 상태가 변하거나 컴포넌트가 사라졌을 때 실행되는 함수다. 이전 useEffect가 마무리 되고 치우는 과정이라 clean up이라 부른다.

useEffect(() => {
  console.log("마운트 또는 업데이트됨");

  return () => {
    console.log("⚠️cleanup 실행!");
  };
}, [의존성 배열]);

1. 컴포넌트가 마운트될 때

  • useEffect가 실행되며 콜백 함수가 실행된다. "마운트 또는 업데이트됨" 출력!
  • 하지만 반환되는 clean up함수는 아직 실행되지 않는다. 이 함수는 useEffect내부에서 적절히 저장된다.

2. 의존성이 변경될 때

  • 이전 useEffect의 저장했던 clean up함수가 실행된다. "⚠️cleanup 실행!" 출력!
  • 이후 새로운 useEffect가 실행된다. "마운트 또는 업데이트됨" 출력!

3. 컴포넌트가 언마운트될 때

  • 마지막으로 clean up함수가 실행된다. "⚠️cleanup 실행!" 출력!

의존성 배열이 없는 경우

useEffect(() => {
  console.log("매 리렌더링 시 실행됨!");
});

리렌더링 시 매번 useEffect가 실행된다. 모든 변경사항을 감지하겠다는 뜻이라 생각해도 된다.

의존성 배열이 빈 배열인 경우

useEffect(() => {
  console.log("마운트 시 한 번만 실행됨!");
}, []);

컴포넌트가 처음 마운트될 때만 실행되고, 이후 상태 변경에는 실행되지 않는다. 상태 변화를 감지하지 않겠다고 생각하면 된다. 따라서 'clean up' 함수는 언마운트 시에만 실행이 된다.

useEffect의 실행 타이밍

의존성 배열이 없을 경우 모든 렌더링마다 useEffect가 실행되므로 useEffect의 콜백함수 return 이전 부분useEffect바깥 부분, 둘 중 어디에 코드를 작성하든 결과가 똑같아 보인다.

function App() {
// 1. 이 부분하고
  useEffect(() => {
    // 2. 이 부분의 차이점이 있나?
    return ()  
  }); 

  return (
  );
}

경우에 따라 같은 결과가 나올 수 있긴 하지만, 두 부분은 실행 타이밍이 아예 다르기 때문에 다르게 생각해야한다.
1번 위치는 렌더링 중에 실행되어 화면이 변경되기 전에 실행되는 반면
2번 위치는 렌더링 이후에 실행되어 화면이 변경된 후 실행된다.

import { useEffect } from "react";

function App() {
  document.title = "렌더링됨"
  console.log("렌더링됨"); // (A)

  useEffect(() => {
    document.title = "이펙트 실행됨"
    console.log("이펙트 실행됨"); // (B)
  });

  return <h1>안녕하세요!</h1>;
}

실행 순서는 다음과 같다 :

1. 렌더링 시작

  • (A) 실행. 화면이 바뀌기 전에 title이 "렌더링됨"으로 바뀐다.
  • JSX 반환(<h1>안녕하세요!</h1>)

2. 리액트가 가상 DOM 업데이트

  • 변경된 내용이 있는지 비교(diffing)

3. 실제 DOM 업데이트

  • 브라우저가 화면을 변경

4. 렌더링 후 useEffect실행

  • (B) 실행. 화면이 바뀐 후 title이 "이펙트 실행됨"으로 바뀐다.

따라서 useEffect의 실행 타이밍은 렌더링 이후, 정확히는 가상 DOM 뿐만 아니라 실제 DOM이 업데이트 된 이후다.

useEffect 사용 지침

useEffect는 개발자에게 있어 상태를 원할 때 변경할 수 있는 강력한 도구다. 그러나 이를 남발하면 유지보수성과 성능에 악영향을 줄 수 있다. 리액트는 외부와의 통신(API 호출, 로컬 스토리지 읽기 등)처럼 부수 효과(side effects)가 필요한 경우에만 사용하고 불필요한 경우 피하는 것을 권장한다.

공식문서 중 Effect가 필요하지 않을 수도 있습니다를 잘 읽어보면 외부 시스템이 관여하지 않는 경우(예를 들어 propsstate가 변경될 때 컴포넌트의 state를 업데이트하려는 경우) 어떻게 useEffect없이 코드를 작성할 수 있는지에 대한 방법들을 제시한다.


라이프사이클

컴포넌트의 라이프사이클

React 컴포넌트의 라이프사이클은 "마운트 → 업데이트 → 언마운트" 순서로 진행된다.
여기서 마운트란, 실제 DOM에 처음 생성되어 화면에 나타나는 것을 말하고, 언마운트는 실제 DOM에서 제거되는 것을 말한다.

단계동작
마운트 (Mounting)컴포넌트가 처음 생성되어 화면에 나타남 (render 발생)
업데이트 (Updating)상태(state)나 프롭스(props)가 변경되어 리렌더링됨
언마운트 (Unmounting)컴포넌트가 제거됨 (더 이상 화면에 존재하지 않음)

useEffect의 라이프사이클

useEffect는 컴포넌트의 라이프사이클과 밀접하게 연관되어 있지만, 동작 방식이 조금 다르다.

useEffect 실행 시점설명
컴포넌트가 마운트될 때useEffect의 콜백 함수 실행 (return 부분 제외)
의존성 배열의 값이 변경될 때 (업데이트)기존 useEffect의 cleanup 실행 → 새로운 useEffect 실행
컴포넌트가 언마운트될 때마지막으로 cleanup 실행

그래서 의존성 배열이 빈 배열일 경우 업데이트 과정이 없으므로 컴포넌트 라이프사이클과 useEffect라이프사이클이 같게 된다.


리액트가 Hooks를 관리하는 방법

리액트는 Hooks에 이런저런 제한 조건을 걸어놨다. 대표적으로 Hooks는 컴포넌트의 최상위 레벨, 또는 커스텀 훅에서만 호출할 수 있다. 조건문, 반복문 등 중첩된 블록 내부에서는 훅을 호출해선 안 된다. 어째서 이런 제한 조건이 생겼을까?

리액트는 Hooks를 배열(연결리스트)로 관리하고 있다. 리액트는 상태들을 저장한 배열을 순회하며 지금 어떤 Hook을 다루고 있는지 판단한다. Hook은 오로지 호출 순서에 의존하고 있다는 뜻이다. 그렇기 때문에 최상위 레벨이 아닌 곳에서 Hook을 호출하게 되면 Hook들의 호출 순서가 꼬일 위험이 생겨 엉뚱한 결과를 초래할 수 있다. 그래서 그런 제한 조건이 생기게 되었다.

어째서 key를 사용하여 객체 형태로는 관리하지 않는 것일까? 요점만 말하면 고려해야 할 사항들이 많아 결국 관리하기 더욱 힘들어지기 때문이다. 이 블로그 글을 읽어보면 도움이 많이 된다.

또한 직접 useStateuseEffect를 배열로 관리하는 간단한 코드를 작성해봤다. 이것도 참고! https://github.com/arendt9797/react-challenge/tree/main/lecture4_making-hooks

profile
내배캠 React 9기 수료 후 Wecommit 풀스택 개발자로 근무중

0개의 댓글