React에서 지연 초기화(Lazy Initialization) 이해하기

created:
last-updated:

React에서 useState를 사용할 때, 초기값을 바로 전달하는 대신 함수 형태로 넘길 수 있다. 이 방식을 지연 초기화(Lazy Initialization) 라고 부른다.

지연 초기화를 사용하면 컴포넌트가 처음 렌더링될 때만 초기값을 계산하고, 이후 렌더링에서는 다시 계산하지 않는다.
특히 비용이 큰 초기 연산이 필요한 경우, 불필요한 연산을 줄여 성능을 최적화할 수 있다.

지연 초기화 예시

아래는 최근에 회사에서, 개인적으로 개발한 프로젝트에서도 로컬스토리지에 값을 저장하고 꺼내와 초기값으로 사용할 일이 있었는데, 그 로직을 추상화한 코드다. localStorage에 저장된 값을 읽어와 상태로 사용하는 커스텀 훅 useLocalStorage으로 만들었다.


import { useState, useEffect } from "react";

export function useLocalStorage<T>(key: string, initialValue: T) {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? (JSON.parse(item) as T) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });

  useEffect(() => {
    try {
      window.localStorage.setItem(key, JSON.stringify(storedValue));
    } catch (error) {
      // 에러 핸들링
    }
  }, [key, storedValue]);

  return [storedValue, setStoredValue] as const;
}


이 코드에서 주목할 부분은 useState의 초기값으로 함수를 전달하고 있다는 점이다.


const [storedValue, setStoredValue] = useState<T>(() => {
  const item = window.localStorage.getItem(key);
  ...
});

만약 useState에 함수가 아닌 결과값을 직접 넣었다면 window.localStorage.getItem 호출이 컴포넌트가 렌더링될 때마다 실행됐을 것이다.
그러나 함수를 전달하면 컴포넌트가 처음 마운트될 때만 이 함수가 호출된다. 이후 렌더링에서는 초기값 계산이 다시 일어나지 않는다.

지연 초기화가 없으면 생길 수 있는 문제

지연 초기화를 사용하지 않는다면 렌더링할 때마다 불필요한 연산이 반복될 수 있다. 예를 들어 다음과 같은 문제가 생긴다.

지연 초기화를 적용하면 이 모든 문제를 깔끔하게 막을 수 있다.

언제 지연 초기화를 사용할까

지연 초기화는 다음과 같은 실제 상황에서 자주 사용된다.

const [isDarkMode, setIsDarkMode] = useState(() => {
  return window.matchMedia('(prefers-color-scheme: dark)').matches;
});

정리