본문 바로가기

useState - Lazy initialize

React 애플리케이션에서 초기 상태를 설정할 때, 복잡한 계산이나 무거운 작업이 필요한 경우가 있습니다. 이러한 상황에서 불필요한 연산을 줄이고 애플리케이션의 성능을 최적화하기 위해 useState의 Lazy Initialize(게으른 초기화) 패턴을 활용할 수 있습니다. 이번 장에서는 Lazy Initialize의 개념과 실제 활용법에 대해 자세히 알아보겠습니다.

1. Lazy Initialize란?

Lazy Initialize는 useState를 사용할 때 초기값을 게으르게(lazy) 설정하는 방법입니다. 일반적으로 useState에 직접 값을 전달하는 대신, 초기값을 반환하는 함수를 전달함으로써 컴포넌트의 첫 렌더링 시에만 해당 함수가 실행되도록 할 수 있습니다. 이는 특히 초기값을 계산하는데 비용이 많이 드는 경우에 유용합니다.

1.1 일반적인 초기화와 Lazy Initialize의 차이

다음 예제를 통해 일반적인 초기화 방식의 문제점을 살펴보겠습니다.

import React, { useState } from 'react';

function expensiveComputation() {
  console.log('비용이 큰 계산 수행중...');
  // 실제로는 복잡한 계산이나 데이터 처리가 일어난다고 가정
  return '계산된 초기값';
}

function Component() {
  // 매 렌더링마다 expensiveComputation 함수가 실행됩니다
  const [value, setValue] = useState(expensiveComputation());

  return (
    <div>
      <p>값: {value}</p>
      <button onClick={() => setValue('새로운 값')}>값 변경</button>
    </div>
  );
}

이 방식의 문제점은 컴포넌트가 리렌더링될 때마다 expensiveComputation 함수가 불필요하게 실행된다는 것입니다. React는 실제로 이 값을 사용하지 않음에도 불구하고, 함수가 매번 실행되어 성능에 영향을 미칠 수 있습니다.

이제 Lazy Initialize를 사용하여 이 문제를 해결해보겠습니다.

function Component() {
  // 초기 렌더링 시에만 expensiveComputation 함수가 실행됩니다
  const [value, setValue] = useState(() => expensiveComputation());

  return (
    <div>
      <p>값: {value}</p>
      <button onClick={() => setValue('새로운 값')}>값 변경</button>
    </div>
  );
}

2. Lazy Initialize 실전 활용

2.1 로컬 스토리지와 함께 사용하기

실제 애플리케이션에서 자주 사용되는 예시로, 로컬 스토리지의 데이터를 초기값으로 사용하는 경우를 살펴보겠습니.

function UserPreferences() {
  const [preferences, setPreferences] = useState(() => {
    // 로컬 스토리지 접근은 비용이 큰 작업일 수 있습니다
    const savedPreferences = localStorage.getItem('userPreferences');
    return savedPreferences
      ? JSON.parse(savedPreferences)
      : {
          theme: 'light',
          notifications: true,
        };
  });

  const updatePreferences = (newPreferences) => {
    setPreferences(newPreferences);
    localStorage.setItem('userPreferences', JSON.stringify(newPreferences));
  };

  return (
    <div>
      <h2>사용자 설정</h2>
      <label>
        <input
          type="checkbox"
          checked={preferences.notifications}
          onChange={(e) =>
            updatePreferences({
              ...preferences,
              notifications: e.target.checked,
            })
          }
        />
        알림 받기
      </label>
    </div>
  );
}

2.2 복잡한 초기 상태 계산

여러 props를 기반으로 초기 상태를 계산해야 하는 경우의 예시입니다.

function DataGrid({ initialData, filters, sortOrder }) {
  const [processedData, setProcessedData] = useState(() => {
    // 복잡한 초기 데이터 처리 로직
    let data = [...initialData];

    // 필터 적용
    if (filters) {
      data = data.filter((item) => filters.every((f) => f(item)));
    }

    // 정렬 적용
    if (sortOrder) {
      data.sort((a, b) => sortOrder(a, b));
    }

    return data;
  });

  return (
    <table>
      <tbody>
        {processedData.map((item) => (
          <tr key={item.id}>
            <td>{item.name}</td>
            <td>{item.value}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

3. 주의사항과 최적화 전략

Lazy Initialize는 강력한 최적화 도구이지만, 모든 상황에서 필요한 것은 아닙니다. 다음과 같은 사항들을 고려해야 합니다.

첫째, 초기값 계산이 정말 비용이 큰 작업인지 확인해야 합니다. 단순한 값 할당이나 간단한 계산의 경우에는 Lazy Initialize를 사용할 필요가 없습니다. 오히려 코드만 복잡해질 수 있습니다.

둘째, Lazy Initialize는 컴포넌트의 초기 렌더링 성능만 최적화합니다. 이후의 상태 업데이트나 리렌더링 성능과는 관계가 없습니다. 렌더링 성능을 최적화하기 위해서는 useMemo나 useCallback과 같은 다른 훅을 고려해야 할 수 있습니다.

셋째, Lazy Initialize 함수 내에서 부수 효과(side effects)를 발생시키지 않도록 주의해야 합니다. API 호출이나 이벤트 리스너 등록과 같은 작업은 useEffect 훅을 통해 처리해야 합니다.

마지막으로, Lazy Initialize와 useMemo의 차이점을 이해하고 적절히 사용하는 것이 중요합니다. Lazy Initialize는 초기 상태 설정만을 위한 것이며, 이후의 계산된 값 최적화는 useMemo를 사용해야 합니다. 실제 프로젝트에서 Lazy Initialize를 효과적으로 사용하기 위해서는, 성능 측정 도구를 활용하여 실제 최적화가 필요한 부분을 파악하고, 해당 부분에 선별적으로 적용하는 것이 바람직합니다. React DevTools의 Profiler를 활용하면 컴포넌트의 렌더링 성능을 측정하고 최적화가 필요한 부분을 식별하는 데 도움이 될 수 있습니다.

useState - Lazy initialize - React 에센셜 | 위니버시티