완벽 가이드 - Side Effect

primav·2024년 12월 5일

React

목록 보기
25/35
post-thumbnail

📌 Side Effect

side effect란 부수효과라는 뜻으로, 컴포넌트 렌더링 과정에서 UI를 직접적으로 변경하지 않는 작업(예: 데이터 가져오기, 이벤트 구독 등)을 의미한다.

부수효과의 문제점 - 무한루프

useState 를 사용하여 상태를 관리하면 값이 바뀔 때마다 해당 컴포넌트가 리렌더링된다.
이렇게 되면 다시 컴포넌트가 실행되고, 위치 값이 또 바뀌므로 또 렌더링 .. 이렇게 무한루프가 진행된다.

✔️ Before

function App() {
 	const [availablePlaces, setAvailablePlaces] = useSTate([]);
  
  navigator.geolocation.getCurrentPosition((position) => {
   	const sortedPlaces = sortPlacesByDistance(
    	position.coords.latitude,
      	position.coords.longitude
      );
    
    setAvailablePlaces(sortedPlaces);
  }
}

해결방법 : useEffect 사용

📌 useEffect

useEffect 란?

  • 컴포넌트가 렌더링된 이후에 실행되는 부수효과 관리 함수이다.
  • 부수효과란?
    컴포넌트의 렌더링 외에 추가적으로 필요한 작업들(예: 데이터 가져오기, 구독 설정, DOM 수정 등)을 의미한다.

useEffect의 기본 구조

useEffect(() => {
  // 부수효과 코드 (예: 데이터 가져오기, 이벤트 설정 등)
  return () => {
    // 정리(cleanup) 코드 (예: 구독 해제, 타이머 정리 등)
  };
}, [의존성 배열]);

주요 특징

  • 렌더링 이후 실행 - 렌더링에 영향을 미치지 않으며, UI가 갱신된 후 실행된다.
  • 의존성 배열 - 부수효과가 실행되는 조건을 설정할 수 있다.
  • 정리(cleanup) - 컴포넌트가 언마운트되거나 부수효과가 다시 실행되기 전에 정리 작업을 수행할 수 있다.

반환값

useEffectundefined를 반환한다.

사용 예시

1. 컴포넌트가 마운트될 때 한 번 실행

의존성 배열을 빈 배열([])로 설정하면, 컴포넌트가 처음 렌더링될 때만 실행

useEffect(() => {
  console.log("컴포넌트가 마운트되었습니다!");
}, []);

2. 상태나 props가 변경될 때 실행

특정 상태나 props가 변경될 때만 실행되도록 의존성 배열에 값을 추가

useEffect(() => {
  console.log("count 상태가 변경되었습니다:", count);
}, [count]); // count가 변경될 때 실행

3. 정리(cleanup) 작업

타이머, 이벤트 리스너, 구독 등을 설정한 경우 정리 작업을 추가 가능

  • return 문으로 정리한다.
  • 첫번째로 useEffect (부가 효과)가 실행되고 다시 useEffect가 실행되기 이전에 (사이에) 정리가 실행된다.
  • 정리 작업이 실행되는 시점

    • 의존성 배열([])이 비어 있으면, 컴포넌트가 언마운트될 때 정리 작업이 실행
    • 의존성 배열에 값이 있으면, 의존성이 변경되기 전에 정리 작업이 실행된 후 새로운 부가 효과가 설정
// 예를 들면, 타이머 기능에서 정리를 하지 않으면 계속 타이머가 계속 진행된다.
useEffect(() => {
  const timer = setInterval(() => {
    console.log("1초마다 실행");
  }, 1000);

  return () => {
    clearInterval(timer); // 타이머 정리
    console.log("타이머가 정리되었습니다!");
  };
}, []);

언제 사용할까?

  • 데이터 가져오기 (Fetching Data)
    API 호출 등 비동기 작업 수행 시

  • 구독
    WebSocket, 이벤트 리스터, Redux 구독 등

  • DOM 조작
    컴포넌트가 특정 DOM 요소와 상호작용해야 할 때

  • 타이머 설정 및 정리
    setTimeout / setInterval

✔️ After

function App() {
 	const [availablePlaces, setAvailablePlaces] = useSTate([]);
  
  	useEffect(() => {
		navigator.geolocation.getCurrentPosition((position) => {
   		const sortedPlaces = sortPlacesByDistance(
    		position.coords.latitude,
      		position.coords.longitude
     	 );
    
   	 setAvailablePlaces(sortedPlaces);
 	 }
	}, []);
}

위 코드대로 하면 아까처럼 무한 루프가 되는 문제점이 발생하지 않는다.
컴포넌트가 모두 실행된 후 useEffect 안의 코드가 한번만 실행된다.

하지만 모든 부수 효과에 useEffect가 필요한 것은 아니다.

위의 예시에서는 현재 위치를 구하는 상태가 게속 업데이트 되면서 무한루프가 생겨 이를 막기 위해 useEffect를 사용한 것이고, 데이터를 저장하는 등 문제가 생기지 않는 경우도 있다.

👀 그럼 언제 useEffect 를 사용할까?

  1. 무한루프가 발생할 때 (막기 위해)
  2. 컴포넌트 함수가 최소 한번 실행된 이후에 작동이 가능한 코드가 있을 때

예시) 타이머

아래와 같은 상황에서는 useEffect를 사용하지 않으면 사용자가 취소 버튼을 눌러도 onConfirm이 실행된다. 컴포넌트가 실행되면 setTimeout 은 무조건 한번 실행되기 때문이다.

이 때 필요한 것이 useEffect 이다.

✔️ Before

setTimeout(() => {
  onConfirm();
}, 3000)

return (
 	<>
    	<button onClick={onCancel}>No</button>
    	<button onClick={onConfirm}>Yes</button>
    </>
 )

✔️ After (useEffect 사용)

useEffect(() => {
 setTimeout(() => {
  onConfirm();
}, 3000);
  
  return () => {
   clearTimeout(timer); 
  }
}, [onConfirm]);

return (
 	<>
    	<button onClick={onCancel}>No</button>
    	<button onClick={onConfirm}>Yes</button>
    </>
 )

📌 useCallback

useCallback 이란?

리액트 훅으로, 불필요한 함수 재생성을 방지하고, 컴포넌트의 성능을 최적화하기 위해 사용된다.

주요 특징

1. 함수의 재생성 방지

  • 원래는 컴포넌트가 리렌더링될 때, 함수는 매번 새로 생성된다.
  • useCallback 은 의존성 배열을 기준으로 함수를 기억해두며 (메모이제이션), 의존성 값이 변경되지 않으면 동일한 함수 인스턴스를 재사용한다.

2. 컴포넌트의 성능 최적화

  • 함수가 자식 컴포넌트에 전달된 경우, 매번 새로운 함수가 생성되면 자식 컴포넌트도 불필요하게 리렌더링된다.
  • useCallback 으로 생성된 함수를 전달하면, 자식 컴포넌트는 React.memo와 함께 최적화할 수 있다.

사용방법

const memoizedCallback = useCallback(
  () => {
    // 실행할 로직
  },
  [dependency1, dependency2] // 의존성 배열
);

언제 사용할까?

  • 자식 컴포넌트에 콜백을 전달하는 경우
    부모 컴포넌트가 리렌더링될 때, 자식 컴포넌트가 불필요하게 리렌더링되지 않도록 최적화한다.

  • 의존성 값에 따라 함수 로직이 변경되어야 하는 경우
    특정 값이 변경되었을 때만 새로운 함수가 필요하다면 useCallback으로 관리할 수 있다.

  • 최적화가 필요한 컴포넌트
    복잡한 컴포넌트 트리에서 불필요한 리렌더링을 줄이기 위해 사용된다.

❗️ 무조건 사용하는 게 좋은 것이 아니다.
useCallback을 사용하는 데도 비용이 발생한다.
함수 메모이제이션 비용이 리렌더링 비용보다 큰 경우, 오히려 성능이 저하되므로 신중하게 사용하자!

0개의 댓글