[React] Effect의 생명주기

우지끈·2024년 11월 7일
post-thumbnail

React 컴포넌트의 생명주기와 Effect의 생명주기

React에서 모든 컴포넌트는 동일한 생명주기 단계를 거친다.

  1. 마운트: 컴포넌트가 화면에 처음 추가될 때
  2. 업데이트: 컴포넌트가 propsstate가 변경될 때마다 업데이트
  3. 언마운트: 컴포넌트가 화면에서 제거될 때

이러한 생명주기 단계는 컴포넌트가 화면에 나타나고, 업데이트되고, 사라지는 전체적인 흐름을 이해하는 데 매우 중요하다.

하지만, Effect컴포넌트와는 별도의 생명주기를 가진다. useEffect를 사용하면 컴포넌트 생명주기와는 약간 다른 방식으로 특정 작업을 실행하고 정리할 수 있다. Effect의 생명주기는 컴포넌트 생명주기와 달리 동기화라는 측면에서 다뤄진다.


Effect의 생명주기 단계

Effect의 생명주기는 크게 두 단계로 나눌 수 있다.

  1. 동기화의 시작: 어떤 특정한 상태props에 맞춰 외부 시스템과 동기화를 시작한다. 예를 들어, 데이터를 가져오거나 이벤트 리스너를 등록하는 작업이 이에 해당한다.
  2. 동기화의 중지: 더 이상 동기화가 필요하지 않을 때 중지(clean-up())한다. 컴포넌트가 언마운트될 때나, 특정 상태나 props가 변경되어 새로운 Effect가 필요할 때 기존 동기화를 정리하는 과정을 말한다.

때에 따라 마운트된 상황에서 동기화를 여러 번 시작하고 중지할 수도 있다.(밑에서 설명 예정)


Effect와 컴포넌트의 생명주기가 다른 이유

컴포넌트와 Effect가 서로 다른 생명주기를 가지는 이유는, Effect는 외부와의 동기화 작업을 주로 담당하기 때문이다. 예를 들어, 컴포넌트 내부에서 사용하는 상태는 컴포넌트 자체가 알아서 관리할 수 있지만, API 호출, 이벤트 리스너 설정, 타이머 동작과 같은 작업은 컴포넌트 외부 시스템과의 연결을 필요로 한다.

이러한 동기화 작업은 특정 시점에만 필요하며, 불필요한 자원이 사용되지 않도록 필요할 때 시작하고 필요 없을 때 중지하는 것이 중요하기 때문이다.


동기화의 반복

React에서 Effect는 컴포넌트의 생명주기와 독립적으로 작동하며, 컴포넌트가 마운트된 상태에서도 여러 번 시작하고 중지될 수 있다. 아래 예시를 통해 살펴보자.

ChatRoom 컴포넌트가 사용자가 드롭다운에서 선택한 roomId prop을 받는다고 가정해 보자. 처음에 사용자가 "general" 대화방을 roomId로 선택했다고 가정해 했을 때, 앱에는 "general" 채팅방이 표시될 것이다.

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId /* "general" */ }) {
  // ...
  return <h1>Welcome to the {roomId} room!</h1>;
}

UI가 표시되면 React가 effect를 실행하여 동기화를 시작하고 "general" 방에 연결된다.

function ChatRoom({ roomId /* "general" */ }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId); // "general" 방에 연결
    connection.connect();
    return () => {
      connection.disconnect(); // "general" 방에서 연결 해제
    };
  }, [roomId]);
  // ...

그런데 여기서 사용자가 드롭다운에서 다른 방(예: "travel")을 선택하는 경우를 생각해봐야 한다. 우선 React가 UI를 먼저 업데이트 할 것이다.

function ChatRoom({ roomId /* "travel" */ }) {
  // ...
  return <h1>Welcome to the {roomId} room!</h1>;
}

사용자는 UI에서 "travel"이 선택된 대화방임을 알 수 있지만 지난번에 실행된 effect는 여전히 "general" 대화방에 연결되어 있을 것이다. roomId prop이 변경되었기 때문에 이전에 effect가 수행한 작업("general" 방에 연결)이 더 이상 UI와 일치하지 않게 될 것이다.

따라서 리액트는 다음과 같이 동작할 것이다.

  1. 이전 roomId와의 동기화 중지("general" 방에서 연결 끊기)
  2. 새 roomId와 동기화 시작("travel" 방에 연결)

이러한 과정을 통해 React는 항상 최신 상태와의 일관성을 유지하며, 사용자가 선택한 방에 맞춰 Effect를 적절히 정리하고 다시 동기화할 수 있다.


React가 effect를 재동기화하는 방법

앞서 다룬 것처럼 React 컴포넌트의 생명주기와 Effect의 생명주기는 다르게 동작하고, 컴포넌트가 업데이트될 때마다 Effect는 동기화 작업을 반복한다. 이 과정을 보다 명확히 이해하기 위해 ChatRoom 컴포넌트의 예시를 들어보겠다.

function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId); // roomId에 해당하는 방에 연결
    connection.connect();
    
    return () => {
      connection.disconnect(); // 현재 방에서 연결 해제
    };
  }, [roomId]); // roomId가 변경될 때마다 effect 재실행
}

이 코드의 흐름을 단계별로 설명하면 다음과 같다.

  1. 초기 마운트:
    • 컴포넌트가 처음 렌더링될 때 roomId"general"이라고 가정
    • Effect는 "general" 방에 연결을 설정
  2. 업데이트 시 동기화 중지 및 재시작:
    • roomId"general"에서 "travel"로 변경
    • React는 이전에 설정된 Effect를 정리하기 위해 정리 함수(return())를 호출 → 정리 함수가 "general"방에서의 연결 끊음
    • 이어서 새로운 roomId에 맞춰 Effect가 다시 실행되어 "travel" 방에 연결
  3. 또 다른 업데이트:
    • 사용자가 방을 "music"으로 변경하면 React는 다시 Effect를 정리하여 "travel" 방에서의 연결을 끊고, "music" 방에 새로운 연결을 설정
  4. 컴포넌트 언마운트:
    • 사용자가 다른 화면으로 이동하면 ChatRoom 컴포넌트가 언마운트 됨
    • 이때 마지막으로 Effect의 정리 함수가 호출되어 "music" 방에서의 연결이 끊어짐

이처럼 roomId의 변경에 따라 새로운 방으로의 연결이 Effect의 재동기화를 통해 이루어진다. 따라서 Effect는 컴포넌트가 화면에 있는 동안 여러 번 시작하고 중지될 수 있다는걸 명확히 알 수 있다.


Effect의 관점에서 바라보기

이제 Effect 자체의 관점에서 무슨 일이 일어나는지 생각해 보자. 앞선 ChatRoom 예시에서 실제로 어떤 일이 일어나는지를 Effect의 연속적인 과정으로 정리해 보면 다음과 같다.

  1. "general" 방에 연결된 Effect:
    • 방에 연결을 설정하고 있다가, 더 이상 필요하지 않게 되면 연결 끊음
  2. "travel" 방에 연결된 Effect:
    • "general" 방의 연결이 끊어지고 "travel" 방으로 새롭게 연결
  3. "music" 방에 연결된 Effect:
    • "travel" 방의 연결이 끊어지고 "music" 방에 연결
  4. "music" 방에서 연결이 끊어진 Effect (컴포넌트 언마운트):
    • 이제 더 이상 채팅방에 연결할 필요가 없으므로 연결 완전히 해제

이러한 과정을 통해 각 Effect는 하나의 동기화 작업을 시작하고, 필요할 때 종료하게 된다. 이는 컴포넌트 생명주기의 특정 이벤트에 의해 결정되지 않고, 의존성 배열에 지정된 값의 변경에 의해 결정된다.


정리

  • React 컴포넌트는 마운트 → 업데이트 → 언마운트의 생명주기를 따르지만, Effect는 동기화의 시작과 중지로 구성된 독립적인 생명주기를 가진다.
  • 컴포넌트가 마운트된 상태에서도 props나 상태의 변경에 따라 Effect는 여러 번 동기화될 수 있다.
  • Effect를 사용할 때는 컴포넌트의 생명주기를 복잡하게 고려하기보다는, 단순히 동기화 작업을 어떻게 시작하고 중지할지에 집중할 것

0개의 댓글