[React] useEffect 파헤치기

Narcoker·2023년 9월 13일
0

React

목록 보기
27/32

useEffect란?

React component가 렌더링 되거나(상태값이 변하거나) 의존성 배열에 존재하는 값의 참조값이 변경될 때
특정 작업(Side Effect)을 실행할 수 있도록 하는 리액트 Hook이다.

Side effect
component 렌더링 된 이후에 비동기로 처리되어야 하는 부수적인 효과

useEffect는 component가 mount 됐을 때,
component가 unmount 됐을 때, component가 update 됐을 때,

를 분기하여 특정 작업을 처리할 수 있다.

즉, 클래스형 컴포넌트에서 사용할 수 있었던 생명주기 메소드를
함수형 컴포넌트에서도 사용가능하다.

componentDidMount, componentDidUpdate, componentWillUnmount

useEffect의 반환값은 undefined이다.

파라미터

setup 함수

component가 mount 됐을 때,
component가 unmount 됐을 때, component가 update 됐을 때 실행되야할 함수

선택적으로 cleanup 함수를 작성할 수도 있다.
cleanup 함수는 setup 함수의 return 문에 작성을 하여 설정할 수 있다.

optional dependencies

배열형으로, props, state, 함수 등 표현식이면 값으로 넣을 수 있다.

useEffect setup 함수에 작성한 모든 변수와 함수가 포함된다.
만약 lint for React 가 존재한다면 종속성이 올바로 지정되었는지 확인한다.

React는 Object.is() 메서드를 사용하여 각 의존성을 이전 값과 비교한다.
Object.is()는 === 과 거의 동일하며 값과 타입이 모두 일치해야한다.

만약 dependencies 배열이 비어있다면 컴포넌트가 렌더링될때만 useEffect가 실행된다.

주의사항

React Hook은 컴포넌트 내부의 최상단에서만 사용가능하다.
즉, 반복문이나 조건문 등 다른 블록에 존재해서는 안된다.

외부 시스템과 동기화하는 것이 아니라면 useEffect 가 꼭 필요하지 않을 수 있으니 고려해야한다.

외부 시스템이란 React로 제어되지 않는 모든 코드를 의미한다.

  • setInterval(), clearInterval() 을 사용한 타이머
  • window.addEventListener(), window.removeEventListener() 같은 이벤트 리스너
  • animation.start(), animation.reset() 같은 API가 포함된 타사 애니메이션 라이브러리

의존성 배열의 값이, 상태값이 아닌 컴포넌트 내부에 정의된 객체인 경우
useEffect 가 필요 이상으로 실행될 위험이 있다.

컴포넌트가 렌더링되면 컴포넌트 코드를 처음부터 실행되는데
이때 객체를 새로 만들면서 참조값(주소값)이 변경되게 된다.
이 객체는 의존성 배열에 존재하므로 useEffectsetup 함수가 다시 실행된다.

이 문제를 해결하려면 불필요한 객체를 의존성 배열에서 제거해야한다.

클릭과 같은 상호작용으로 인해 이펙트가 발생한 것이 아니라면,
React는 이펙트를 실행하기 전에 브라우저가 업데이트된 화면을 먼저 그리도록 할 것이다.

useEffect가 시각적인 작업(예: tooltip 위치 지정)을 하고 있는데
깜박임 등의 지연이 눈에 띄는 경우, useEffectuseLayoutEffect로 바꾸는 것을 권장한다.

useEffectCSR 에서만 작동한다. SSR에서는 작동하지 않는다.
Effects only run on the client. They don’t run during server rendering.

Strict Mode 인 경우 React는 setupcleanup 함수를 한번 더 수행한다.
이 의도는 두 함수가 올바르게 구현되었는지 확인하는 스트레스 테스트이다.

예시가 없어서 아직 감이 안온다.

컴포넌트가 unmount되지 않았는데도 cleanup이 실행되는 경우가 발생하는 경우가 있을 것이다.
cleanupummount시 뿐만 아니라 변경된 종속성으로 다시 렌더링하기 전에 실행된다.
또한 개발 시 React는 컴포넌트 mount 직후에 setup+cleanup을 한 번 더 실행한다.

setupcleanup은 다음과 같이 대칭적인 로직이어야 한다.

useEffect(() => {
  const connection = createConnection(serverUrl, roomId);
  connection.connect();
  return () => {
    connection.disconnect();
  };
}, [serverUrl, roomId]);

setup과 cleanup에서 사용하는 상태값

컴포넌트가 재렌더링되는 과정은 다음과 같다.
현재 컴포넌트 unmount -> 새로운 컴포넌트 mount

컴포넌트가 unmount 되면 cleanup 함수가 수행되는데
이때 사용되는 상태값은 setState() 로 상태값이 변하기 이전의 값을 사용한다.

그리고 새로운 컴포넌트가 mount 되면 setup 함수가 수행되는데
이때 사용되는 상태값은 setState() 로 상태값이 업데이트된 값을 사용한다.

의존성 배열의 따른 setup 함수 실행 시점

의존성 배열의 값이 존재하는 경우

useEffect(() => {
  // ...
}, [a, b]); // Runs again if a or b are different
  • 초기 렌더링(페인팅) 이후 실행
  • 의존성 배열의 참조값 변경 시 실행

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

useEffect(() => {
  // ...
}, []); // Does not run again (except once in development)
  • 초기 렌더링(페인팅) 이후에만 실행

파라미터에 의존성 배열이 없는 경우

useEffect(() => {
  // ...
}, []); // Always runs again
  • 컴포넌트의 모든 렌더링(및 재렌더링) 시 실행

이전 상태를 기반으로 상태 업데이트시 주의점 - 타이머

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(count + 1); // You want to increment the counter every second...
    }, 1000)
    return () => clearInterval(intervalId);
  }, [count]); // 🚩 ... but specifying `count` as a dependency always resets the interval.
  // ...
}

1초마다 상태값 count 를 1씩 증가시키기 위해 다음과 같은 useEffect를 만들었다.
하지만 이렇게하면 count 값이 변경될 때마다(1초마다) cleanup 함수가 수행되므로
좋지 않은 예시이다.

따라서 아래와 같이 사용하는 것이 이상적이다.

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(c => c + 1); // ✅ Pass a state updater
    }, 1000);
    return () => clearInterval(intervalId);
  }, []); // ✅ Now count is not a dependency

  return <h1>{count}</h1>;
}

의존성 배열에서 count 를 삭제했고 setCount에서 이전 값을 사용하기 위해
콜백함수를 파라미터로 지정했다.

이렇게하면 cleanup 함수는 위 코드와 달리 unmount 될 때만 수행된다.

profile
열정, 끈기, 집념의 Frontend Developer

0개의 댓글