icon안동민 개발노트

useEffect 훅으로 사이드 이펙트 관리하기


 useEffect 훅은 React 컴포넌트에서 부수 효과(side effects)를 수행하기 위해 사용됩니다.

 부수 효과란 데이터 페칭, 구독 설정, 수동으로 DOM 조작하기 등 컴포넌트의 주요 렌더링 작업 외에 수행해야 하는 모든 작업을 말합니다.

useEffect의 기본 개념

 useEffect 훅의 기본 형태는 다음과 같습니다.

useEffect(() => {
  // 부수 효과를 수행하는 코드
  return () => {
    // 정리(cleanup) 함수
  };
}, [dependencies]);
  • 첫 번째 인자 : 효과를 수행하는 함수
  • 두 번째 인자 : 의존성 배열 (선택적)
  • 반환값 : 정리(cleanup) 함수 (선택적)

의존성 배열의 역할

 의존성 배열은 useEffect가 언제 실행되어야 하는지를 React에게 알려줍니다.

  1. 빈 배열 [] : 컴포넌트가 마운트될 때만 실행
  2. 의존성 포함 [dep1, dep2] : 해당 의존성이 변경될 때마다 실행
  3. 생략 : 모든 렌더링 후 실행
// 마운트 시에만 실행
useEffect(() => {
  console.log('Component mounted');
}, []);
 
// count가 변경될 때마다 실행
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]);
 
// 매 렌더링마다 실행
useEffect(() => {
  console.log('Component updated');
});

클린업 함수의 중요성

 클린업 함수는 컴포넌트가 언마운트되거나 의존성이 변경되어 효과가 다시 실행되기 전에 호출됩니다.

 이는 메모리 누수를 방지하고 불필요한 작업을 정리하는 데 중요합니다.

useEffect(() => {
  const subscription = subscribeToData(dataSource);
  return () => {
    subscription.unsubscribe();
  };
}, [dataSource]);

조건부 효과 구현

 효과 내부에서 조건문을 사용하여 조건부 효과를 구현할 수 있습니다.

useEffect(() => {
  if (isOnline) {
    // 온라인 상태일 때만 실행되는 코드
  }
}, [isOnline]);

 주의 : useEffect 자체를 조건부로 호출하는 것은 피해야 합니다.

실제 사용 사례

  1. 데이터 페칭
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
 
  useEffect(() => {
    const fetchUser = async () => {
      setLoading(true);
      try {
        const response = await fetch(`https://api.example.com/users/${userId}`);
        const data = await response.json();
        setUser(data);
      } catch (error) {
        console.error('Error fetching user:', error);
      } finally {
        setLoading(false);
      }
    };
 
    fetchUser();
  }, [userId]);
 
  if (loading) return <div>Loading...</div>;
  if (!user) return <div>User not found</div>;
 
  return <div>{user.name}</div>;
}

 이 예제에서는 userId가 변경될 때마다 사용자 데이터를 다시 불러옵니다.

  1. 이벤트 리스너 관리
function WindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
 
  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
 
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);
 
  return <div>Window width: {width}px</div>;
}

 이 컴포넌트는 창 크기 변경을 감지하고 현재 너비를 표시합니다.

 클린업 함수에서 이벤트 리스너를 제거하여 메모리 누수를 방지합니다.

  1. DOM 조작
function AutoFocusInput() {
  const inputRef = useRef(null);
 
  useEffect(() => {
    inputRef.current.focus();
  }, []);
 
  return <input ref={inputRef} />;
}

 이 컴포넌트는 마운트 시 자동으로 입력 필드에 포커스를 줍니다.

  1. 타이머 관리
function Timer() {
  const [seconds, setSeconds] = useState(0);
 
  useEffect(() => {
    const timer = setInterval(() => {
      setSeconds(prevSeconds => prevSeconds + 1);
    }, 1000);
 
    return () => clearInterval(timer);
  }, []);
 
  return <div>Seconds: {seconds}</div>;
}

 이 컴포넌트는 1초마다 카운터를 증가시키고, 언마운트 시 타이머를 정리합니다.

useEffect 사용 시 주의사항

  1.  무한 루프 피하기 : 의존성 배열에 포함된 값을 효과 내에서 변경하면 무한 루프가 발생할 수 있습니다.

  2.  불필요한 효과 피하기 : 모든 상태 변경에 대해 효과를 실행할 필요는 없습니다. 필요한 경우에만 의존성을 추가하세요.

  3.  의존성 배열 정확히 지정하기 : eslint-plugin-react-hooks의 exhaustive-deps 규칙을 사용하여 의존성 배열을 정확히 지정하세요.

  4.  비동기 작업 처리 : 비동기 작업을 수행할 때는 경쟁 상태(race condition)를 주의해야 합니다.

useEffect(() => {
  let isCancelled = false;
 
  async function fetchData() {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    if (!isCancelled) {
      setData(data);
    }
  }
 
  fetchData();
 
  return () => {
    isCancelled = true;
  };
}, []);
  1. 상태 업데이트 시기 : 효과 내에서 상태를 업데이트할 때는 무한 루프를 피하기 위해 조건을 신중히 고려해야 합니다.

 useEffect 훅은 React 컴포넌트에서 부수 효과를 관리하는 강력한 도구입니다.

 데이터 페칭, 구독 설정, DOM 조작 등 다양한 시나리오에서 활용될 수 있으며, 의존성 배열과 클린업 함수를 적절히 사용하면 효과적으로 컴포넌트의 생명주기를 관리할 수 있습니다.

 효과의 의존성을 정확히 지정하고, 불필요한 효과 실행을 피하며, 클린업 함수를 통해 리소스를 정리하는 것이 중요합니다.

 또한, 비동기 작업을 다룰 때는 경쟁 상태와 같은 문제를 주의해야 합니다.

 useEffect를 올바르게 사용하면 컴포넌트의 동작을 더 예측 가능하고 관리하기 쉽게 만들 수 있으며, 이는 더 안정적이고 성능이 좋은 React 애플리케이션을 개발하는 데 도움이 됩니다.