useEffect 최적화 하기 (의존성 배열에 condition 전달)

useEffect는 componentDidMount + componentDidUpdate + componentWillUnmount 의 조합이다.

여러 라이프사이클에 해당하는 기능을 useEffect 하나로 모두 처리할 수 있다는건 편리하지만 불필요하게 effect가 실행돼서 리소스 낭비가 발생할 가능성도 높다.

그래서 보통은 useEffect를 원하는 시점에 선택적으로 실행하기 위해 두 번째 파라미터인 의존성 배열을 사용한다.

useEffect에 빈 배열을 전달해서 컴포넌트가 화면에 맨 처음 렌더링될 때만 실행하거나(componentDidMount), 배열에 특정 값(들)을 전달해서 특정 값이 변경됐을때만 useEffect를 실행하는 것이다.(componentDidUpdate)

그런데 만약 의존성 배열에 전달한 값이 변했지만 effect를 실행할 필요가 없는 경우는 어떻게 해야 할까?

아래 예시처럼 useEffect의 첫번째 인자인 콜백 함수를 조건에 따라 실행하는 경우를 살펴보자.

import React, { useEffect } from "react";
import { useSelector } from "react-redux";
import { useHistory } from "react-router-dom";

const ProfilePage = () => {
  const { me } = useSelector(state => state.user);
  let history = useHistory();

  useEffect(() => {
    if (!(me && me.id)) history.push("/");
  }, [me, me.id]);

  // ...
};

사용자가 로그인을 하지 않은 상태에서 프로필 페이지에 접근시 메인화면으로 리다이렉트 하는 코드이다.

위 시나리오의 경우 리액트 history.push('/'); 라는 사이드 이펙트를 처리하기 위해 총 2번의 평가 과정을 거쳐야 한다.

  1. useEffect의 의존성 배열에 들어있는 값들(me. me.id)을 하나씩 확인하면서 바뀐 값이 있는지 평가하고 useEffect 실행 여부를 결정한다.
  2. 바뀐 값이 있을 경우 콜백을 실행 → 콜백 내부의 if문을 평가하여 내부 로직 (history.push('/');) 의 수행 여부를 결정한다.

그런데 만약 !(me && me.id)false를 반환하여 history.push('/');를 실행할 필요가 없는 것으로 결론이 난다면?

이 모든 평가 과정에서 불필요한 리소스 낭비가 발생한 셈이다.


이러한 시나리오에 적합한 최적화 방법이 있다. useEffect 실행여부와 콜백 내부의 if문을 평가하고 그 결과값인 boolean을 의존성 배열에 전달하는 것이다.

import React, { useEffect } from "react";
import { useSelector } from "react-redux";
import { useHistory } from "react-router-dom";

const ProfilePage = () => {
  const { me } = useSelector(state => state.user);
  let history = useHistory();

  const isLoggedIn = me && me.id;
  useEffect(() => {
    if (!isLoggedIn) history.push("/");
  }, [isLoggedIn]);

  // ...
};

useEffect의 실행 여부를 미리 계산해서 isLoggedIn이라는 변수에 할당하고, useEffect의 의존성 배열의 인자로 전달한다.

meme.id가 바뀔 때마다 isLoggedInboolean값만 업데이트하면 리액트는 예전처럼 모든 값들을 일일이 확인할 필요 없이 isLoggedIn에 cache된 boolean값을 참조하여 useEffect의 실행 여부를 즉시 알 수 있게 된다. 물론 콜백 내부의 if문도 기존 대비 쉽게 그 결과를 알 수 있다. 평가 과정이 간소화 되면서 useEffect를 실행하는데 필요한 리소스가 줄어든 것이다.

이때 주의할 점은 useEffect와 콜백 내부 코드 실행 여부를 판별하기위한 값(=isLoggedIn)을 얻는데 필요한 리소스가 기존에 리액트가 useEffect를 실행하기 위해 사용하는 리소스보다 적어야 최적화의 의미가 있다는 것이다.

만약 isLoggedIn을 얻기 위해 아주아주 복잡한 로직과 계산 과정이 수반된다면 최적화의 의미가 퇴색될 것이다.


@Reese
Sin Prosa Sin Pausa