icon안동민 개발노트

컴포넌트 라이프사이클 이해하기 (함수형 컴포넌트 중심)


 React 컴포넌트의 라이프사이클은 컴포넌트가 생성되고, 업데이트되며, 제거되는 과정을 말합니다.

 함수형 컴포넌트에서는 useEffect 훅을 사용하여 이러한 라이프사이클 이벤트를 처리합니다.

 이 절에서는 함수형 컴포넌트에서 라이프사이클을 다루는 방법과 클래스형 컴포넌트와의 차이점을 알아보겠습니다.

useEffect 훅 소개

 useEffect 훅은 함수형 컴포넌트에서 부수 효과(side effects)를 수행할 수 있게 해줍니다.

 이는 데이터 가져오기, 구독 설정, 수동으로 DOM 조작하기 등의 작업을 포함합니다.

 기본 구조

useEffect(() => {
  // 부수 효과를 수행하는 코드
  return () => {
    // 정리(clean-up) 함수 (선택사항)
  };
}, [dependencies]);

컴포넌트 마운트 시 작업 수행

 컴포넌트가 처음 렌더링될 때 특정 작업을 수행하려면 useEffect를 다음과 같이 사용합니다.

import React, { useEffect, useState } from 'react';
 
function DataFetcher() {
  const [data, setData] = useState(null);
 
  useEffect(() => {
    // 컴포넌트 마운트 시 데이터 가져오기
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(result => setData(result));
  }, []); // 빈 의존성 배열은 이 효과가 마운트 시에만 실행됨을 의미합니다
 
  return (
    <div>
      {data ? <p>{JSON.stringify(data)}</p> : <p>Loading...</p>}
    </div>
  );
}

 이 예제에서 useEffect는 컴포넌트가 마운트될 때 한 번만 실행됩니다.

컴포넌트 업데이트 시 작업 수행

 특정 값이 변경될 때마다 작업을 수행하려면 useEffect의 의존성 배열에 해당 값을 포함시킵니다.

import React, { useEffect, useState } from 'react';
 
function Counter() {
  const [count, setCount] = useState(0);
 
  useEffect(() => {
    // count가 변경될 때마다 실행
    document.title = `You clicked ${count} times`;
  }, [count]); // count를 의존성 배열에 포함
 
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

 이 예제에서는 count 값이 변경될 때마다 문서의 제목이 업데이트됩니다.

컴포넌트 언마운트 시 정리 작업

 컴포넌트가 언마운트되거나 재렌더링되기 전에 정리 작업을 수행해야 할 때는 useEffect에서 정리 함수를 반환합니다.

import React, { useEffect, useState } from 'react';
 
function Timer() {
  const [seconds, setSeconds] = useState(0);
 
  useEffect(() => {
    const intervalId = setInterval(() => {
      setSeconds(prevSeconds => prevSeconds + 1);
    }, 1000);
 
    // 정리 함수
    return () => {
      clearInterval(intervalId);
    };
  }, []); // 빈 의존성 배열은 이 효과가 마운트와 언마운트 시에만 실행됨을 의미합니다
 
  return <p>Timer: {seconds} seconds</p>;
}

 이 예제에서 정리 함수는 컴포넌트가 언마운트될 때 인터벌을 정리합니다.

클래스형 컴포넌트와의 차이점

 클래스형 컴포넌트에서는 componentDidMount, componentDidUpdate, componentWillUnmount 등의 라이프사이클 메서드를 사용했습니다.

 함수형 컴포넌트에서는 이러한 메서드들이 useEffect 하나로 대체됩니다.

 클래스형 컴포넌트 예제

class ExampleComponent extends React.Component {
  componentDidMount() {
    console.log('Component mounted');
  }
 
  componentDidUpdate(prevProps, prevState) {
    if (this.props.someProp !== prevProps.someProp) {
      console.log('someProp changed');
    }
  }
 
  componentWillUnmount() {
    console.log('Component will unmount');
  }
 
  render() {
    return <div>Example Component</div>;
  }
}

 동등한 함수형 컴포넌트

function ExampleComponent({ someProp }) {
  useEffect(() => {
    console.log('Component mounted');
 
    return () => {
      console.log('Component will unmount');
    };
  }, []); // 마운트와 언마운트 시에만 실행
 
  useEffect(() => {
    console.log('someProp changed');
  }, [someProp]); // someProp이 변경될 때마다 실행
 
  return <div>Example Component</div>;
}

함수형 컴포넌트에서 다루는 것의 장점

  1. 간결성: 여러 라이프사이클 메서드 대신 하나의 useEffect 훅으로 여러 상황을 처리할 수 있습니다.
  2. 관심사의 분리: 연관된 로직을 하나의 useEffect 안에 모을 수 있어, 코드의 구조화가 용이합니다.
  3. 재사용성: 커스텀 훅을 만들어 라이프사이클 관련 로직을 쉽게 재사용할 수 있습니다.
  4. 테스트 용이성: 순수 함수로 작성된 컴포넌트는 테스트하기가 더 쉽습니다.
  5. 성능 최적화: React의 동시성 모드(Concurrent Mode)와 더 잘 동작합니다.

useEffect 사용 시 주의사항

  1. 무한 루프 방지: 의존성 배열을 올바르게 설정하여 불필요한 재실행을 방지해야 합니다.
  2. 정리 함수의 중요성: 구독, 타이머 등의 리소스는 반드시 정리해야 합니다.
  3. 조건부 효과: useEffect 내부에서 조건문을 사용할 수 있지만, useEffect 자체를 조건부로 실행해서는 안 됩니다.
// 잘못된 사용
if (condition) {
  useEffect(() => {
    // ...
  }, []);
}
 
// 올바른 사용
useEffect(() => {
  if (condition) {
    // ...
  }
}, [condition]);

 함수형 컴포넌트와 useEffect 훅을 사용하여 React 컴포넌트의 라이프사이클을 관리하는 것은 더 선언적이고 유연한 방식으로 부수 효과를 다룰 수 있게 해줍니다.

 클래스형 컴포넌트의 라이프사이클 메서드에 비해 더 직관적이고 재사용 가능한 코드를 작성할 수 있으며, React의 최신 기능과도 더 잘 호환됩니다.

 그러나 useEffect를 올바르게 사용하기 위해서는 의존성 배열의 올바른 사용, 정리 함수의 중요성, 그리고 조건부 실행에 대한 이해가 필요합니다. 이러한 개념을 잘 이해하고 적용한다면, 더 효율적이고 유지보수가 쉬운 React 애플리케이션을 개발할 수 있습니다.