본문 바로가기
FrontEnd/React

useEffect 정리

by 감중에홍시 2023. 6. 6.

오늘은 함수형 컴포넌트를 사용할 때, 알아야 할 필수 개념인 useEffect 훅에 대해 정리를 해보고자 한다.

useEffect에 대해 알기 전에 우리는 우선 컴포넌트의 생명주기에 대해 알아야 한다.

 

컴포넌트의 생명주기

우리들도 인생에서 탄생, 성장, 죽음이 있듯이, 컴포넌트에게도 생명주기라는 것이 존재한다.

컴포넌트의 생명주기는 다음과 같이 크게 3가지로 구분된다.

  • 탄생에 해당하는 mount (페이지에 장착되는 행위)
  • 성장(재렌더링)에 해당하는 update (컴포넌트 내용이 업데이트 되는 행위)
  • 죽음에 해당하는 unmount (컴포넌트가 제거되는 행위)

그렇다면 컴포넌트의 생명주기를 왜 알아야 할까?

그 이유는 바로 컴포넌트의 생명주기를 알아야 포넌트의 인생 중간중간에 내가 원하는 효과(기능)을 집어넣을 수 있기 때문이다.

예를 들어서 "컴포넌트가 생성될 때, ~~한 기능을 수행해 줘."라든가, "컴포넌트가 사라질 때, ~~한 기능을 수행해 줘." 등의 프로그래밍이 가능하다.

 

useEffect 사용방법

useEffect의 생명주기에 따른 기본적인 사용 방법은 다음과 같다.

  • mount 예시 코드
  const [alert, setAlert] = useState(true);
  useEffect(() => {
    // 1. 처음 mount 되고 2초 뒤, alert를 false로 만든다.
    let a = setTimeout(() => {
      setAlert(false);
    }, 2000);
  }, []);

 

  • update 예시 코드
  const [alert, setAlert] = useState(true);
  useEffect(() => {
    // 2. count가 바뀔 때마다 alarm state를 변경
    setAlert(!alert);
  }, [count]);

 

  • unmount 예시 코드
  const [alert, setAlert] = useState(true);
  // clean up function 예시
	useEffect(() => {
    // 처음 렌더링되고, 2초 간격으로 alert의 상태를 바꿔준다.
    let a = setInterval(() => {
      setAlert(!alert);
    }, 2000);
    return () => {
      // unmount 될 때, timer 함수를 clear 해준다.(타이머를 없애 준다.)
      // 왜냐하면 컴포넌트가 unmount 됐음에도 timer가 계속 실행되고 있으면 메모리의 낭비가 일어나기 때문
      clearTimeout(a);
    };
  }, []);

 

useEffect의 특징

  • html이 렌더링 된 후에 useEffect 안에 있는 코드가 실행됨(컴포넌트의 return 안에 코드가 렌더링 된 후에, useEffect 안에 있는 코드가 실행된다.)
  • 일단 렌더링이 빨리 되는 것이 중요하기 때문에, 실행이 오래 걸리는 코드를 useEffect 안에 넣어주는 것이 관습이다.
  •  어려운 연산, 서버에서 데이터 가져오는 거, 타이머 장착하는 것들을 보통 useEffect  훅 안에 넣는다.

 

useEffect 사용시 유의사항

실제로 코드를 짜다보면 useEffect가 만능인 줄 알고, useEffect를 남발하는 경우가 있다.(내가 그랬다..) 하지만 useEffect를 남발하다보면 로직이 꼬이는 등, 내가 의도한대로 기능 구현이 안되는 경우가 많다.

리액트 공식문서에서는 보통 다음과 같은 상황일 때, useEffect의 사용을 권장하지 않는다.

1. Updating state based on props or state -> props나 state를 기반으로 state 값을 바꿀 때

2. Caching expensive calculations -> useEffect 안에 로직이 비싼 연산일 경우

※ 1에 대한 예시 코드(Updating state based on props or state)

  function ShowName() {
    const [firstName, setFirstName] = useState('HongHong');
    const [lastName, setLastName] = useState('Kim');
    // Avoid!!!!!! 불필요한 state와 불필요한 Effect
    const [fullName, setFullName] = useState('');
    useEffect(() => {
      setFullName(`${firstName} ${lastName}`);
    }, [firstName, lastName]);
  }

리액트 공식 홈페이지에서는 다음과 같이 코드를 짜는 것을 지양한다!

그 이유를 살펴보자. 

우선, 리액트state 값이 바뀌면 해당 부분이 리렌더링 되는데, 만약 firstName이 바뀌면 state 값이 변경되었기 때문에 리렌더링이 되고, firstName의 state가 변했기 때문에 useEffect 안에 state값이 변경(setFullName) 된다.

이 때, useEffect로 인해 fullName의 state 값이 변경되었기 때문에 이로 인한  렌더링이 또 발생하게 된다.

이는 state 값을 한 번 바꿨을 뿐인데, 렌더링이 세 번이나 되는 현상인 것이다.

따라서 state를 기반으로 state 값을 바꿀 때 useEffect를 사용한다면 불필요한 렌더링이 발생하기 때문에 이런 방식으로 useEffect를 사용하는 것은 바람직하지 않다.

그렇다면 위 코드는 어떻게 고쳐야 바람직할까? 리액트 공식 문서의 추천 방법은 아래와 같다.

  function ShowName() {
    const [firstName, setFirstName] = useState('HongHong');
    const [lastName, setLastName] = useState('Kim');
    // Good!! 렌더링이 되는 동안 fullName의 값이 계산된다.
    const fullName = firstName + lastName;
  }

만약 위와 같이 코드를 짰을 때, firstName의 값이 바뀐다면, 화면이 재렌더링 될 것이다. 그러나 앞선 예시와는 다르게 이번에는 화면이 재렌더링 되는 동안 fullName의 값이 계산(설정)되는 것이기 때문에 불필요한 재렌더링이 발생하지 않게 된다. 이제 2에 대한 예시 코드를 봐보자.

 

※ 2에 대한 예시 코드(Caching expensive calculations)

  function TodoList({ todos, filter }) {
    const [newTodo, setNewTodo] = useState('');
    const [visibleTodos, setVisibleTodos] = useState([]);
    // Avoid!! 불필요한 state와 useEffect 사용
    useEffect(() => {
      setVisibleTodos(getFiltersTodos(todos, filter));
    }, [todos, filter]);
  }

리액트 공식 홈페이지에서는 위와 같이 useEffect 안의 로직이 비싼 연산일 경우, useEffect의 사용을 지양한다!

만약 getFilteredTodos 함수가 비싼 연산일 경우 useEffect 사용은 권장되지 않는다.

비싼 연산이란 큰 배열의 합을 계산하거나, 복잡한 수학적 함수를 계산하는 것 등의 경우를 의미한다.

위와 같이 비싼 연산을 사용하는 경우에는 useEffect를 사용하기보다는 아래와 같이 useMemo의 사용이 권장된다.

import { useState, useMemo } from 'react';

function TodoList({ todos, filter }) {
  const [newTodos, setNewTodos] = useState('');
  // Good!! todo 혹은 filter 값이 바뀌지 않는 이상
  // cost가 큰 getFilteredTodos 함수가 재실행되지 않는다!
  const visibleTodos = useMemo(() => {
    return getFilteredTodos(todos, filter);
  }, [todos, filter]);
}

 

다음 포스팅에는 마찬가지로 리액트에서 많이 쓰이는 훅인 useMemo와 useCallback 훅에 대해 알아보도록 하겠다!

[출처]

리액트 공식 홈페이지 useEffect

코딩애플 "React 리액트 기초부터 쇼핑몰 프로젝트까지!"