본문 바로가기

카테고리 없음

리프팅 스테이트 업(Lifting State Up) 정리

리프팅 스테이트 업은 React에서 사용되는 중요한 디자인 패턴으로, 하위 컴포넌트들 간에 데이터를 공유하기 위해 상태(state)를 상위 컴포넌트로 끌어올리는 기법이다.

기본 개념

  1. 정의: 여러 자식 컴포넌트에서 동일한 데이터를 사용해야 할 때, 그 데이터를 관리하는 상태를 가장 가까운 공통 부모 컴포넌트로 이동시키는 것
  2. 목적:
    • 여러 컴포넌트 간에 일관된 상태 유지
    • 단방향 데이터 흐름 유지
    • 상태 관리 로직 집중화

실제 예시: 온도 변환기

아래는 섭씨와 화씨 온도를 상호 변환하는 컴포넌트에서 리프팅 스테이트 업을 적용한 핵심 코드다.

1. 부모 컴포넌트에서 상태 관리

function Calculator() {
  // 상태를 부모 컴포넌트에서 관리
  const [temperature, setTemperature] = useState('');
  const [scale, setScale] = useState('c');

  const handleCelsiusChange = (temperature) => {
    setScale('c');
    setTemperature(temperature);
  };

  const handleFahrenheitChange = (temperature) => {
    setScale('f');
    setTemperature(temperature);
  };
  
  // 나머지 코드...
}

부모 컴포넌트에서 temperature와 scale 상태를 관리한다. 두 입력 필드가 공유해야 하는 데이터이기 때문에 상태를 끌어올린 것이다.

2. 자식 컴포넌트에 props로 전달

function Calculator() {
  // 앞의 상태 관리 코드...

  // 화면에 표시할 온도값 계산
  const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
  const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

  return (
    <div>
      <TemperatureInput 
        scale="c"
        temperature={celsius}
        onTemperatureChange={handleCelsiusChange}
      />
      
      <TemperatureInput
        scale="f"
        temperature={fahrenheit}
        onTemperatureChange={handleFahrenheitChange}
      />
    </div>
  );
}

부모 컴포넌트는 상태와 이벤트 핸들러를 자식 컴포넌트에 props로 전달한다. 각 입력 필드는 독립적으로 보이지만 실제로는 부모를 통해 서로 동기화된다.

3. 자식 컴포넌트에서 이벤트 처리

function TemperatureInput({ scale, temperature, onTemperatureChange }) {
  const handleChange = (e) => {
    onTemperatureChange(e.target.value);
  };

  return (
    <fieldset>
      <legend>{scale === 'c' ? '섭씨' : '화씨'} 온도 입력:</legend>
      <input
        value={temperature}
        onChange={handleChange}
      />
    </fieldset>
  );
}

자식 컴포넌트는 자체 상태를 갖지 않고, 부모로부터 받은 props를 사용한다. 변경 사항이 발생하면 부모로부터 받은 콜백 함수(onTemperatureChange)를 호출하여 부모 컴포넌트의 상태를 업데이트한다.

장바구니 예시

장바구니 시스템에서도 같은 패턴을 적용할 수 있다.

1. 부모 컴포넌트에서 상태 선언

function ShopApp() {
  // 장바구니 상태를 부모 컴포넌트에서 관리
  const [cartItems, setCartItems] = useState([]);
  
  // 나머지 코드...
}

장바구니 상태는 여러 컴포넌트(상품 목록, 장바구니, 주문 요약)에서 필요하므로 공통 부모 컴포넌트에서 관리한다.

2. 상태 변경 함수 정의

// 장바구니에 상품 추가 함수
const handleAddToCart = (product) => {
  const existingItem = cartItems.find(item => item.id === product.id);
  
  if (existingItem) {
    // 이미 있다면 수량만 증가
    setCartItems(cartItems.map(item => 
      item.id === product.id 
        ? { ...item, quantity: item.quantity + 1 } 
        : item
    ));
  } else {
    // 없다면 새로 추가
    setCartItems([...cartItems, { ...product, quantity: 1 }]);
  }
};

// 장바구니에서 상품 제거 함수
const handleRemoveFromCart = (productId) => {
  // 제거 로직...
};

상태를 변경하는 함수도 상태와 함께 부모 컴포넌트에 정의된다. 이 함수들은 자식 컴포넌트에 props로 전달되어 호출된다.

3. 자식 컴포넌트에 상태와 함수 전달

return (
  <div className="shop-app">
    <h1>리액트 쇼핑몰</h1>
    <div className="app-container">
      {/* 상품 목록 컴포넌트 */}
      <ProductList 
        products={products} 
        onAddToCart={handleAddToCart} 
      />
      
      {/* 장바구니 컴포넌트 */}
      <Cart 
        items={cartItems} 
        onRemoveFromCart={handleRemoveFromCart} 
      />
      
      {/* 결제 요약 컴포넌트 */}
      <OrderSummary items={cartItems} />
    </div>
  </div>
);

각 자식 컴포넌트는 필요한 상태와 상태 변경 함수를 props로 받는다.

4. 자식 컴포넌트에서 props 사용 예

// 상품 목록 컴포넌트
function ProductList({ products, onAddToCart }) {
  return (
    <div className="product-list">
      <ul>
        {products.map(product => (
          <li key={product.id}>
            {product.name} - {product.price}원
            <button onClick={() => onAddToCart(product)}>
              장바구니에 추가
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

// 장바구니 총액 계산 예
function Cart({ items, onRemoveFromCart }) {
  const total = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
  // 나머지 렌더링 로직...
}

ProductList 컴포넌트는 상품을 장바구니에 추가하기 위해 부모로부터 받은 onAddToCart 함수를 사용한다. Cart와 OrderSummary 컴포넌트는 장바구니 상태에 접근하여 필요한 계산을 수행한다.


리프팅 스테이트 업의 실전 팁

  1. 상태 위치 결정하기:
    • 해당 상태를 사용하는 모든 컴포넌트의 가장 가까운 공통 조상을 찾아라
    • 적절한 공통 조상이 없다면 상태만을 위한 새 컴포넌트를 만들어서 계층 구조 상위에 추가할 수도 있다
  2. props 전달 최소화:
    • props drilling이 심해지면 Context API나 상태 관리 라이브러리 사용 고려
  3. 컴포넌트 재사용성:
    • 상태 관리 로직과 UI 렌더링 로직을 분리하면 재사용성이 향상된다
    • 프레젠테이션 컴포넌트와 컨테이너 컴포넌트 패턴 활용
  4. 성능 최적화:
    • React.memo나 useMemo, useCallback 훅을 사용해 불필요한 리렌더링 방지

결론

리프팅 스테이트 업은 React의 기본적인 상태 관리 패턴으로, 여러 컴포넌트 간에 데이터를 동기화할 때 매우 유용하다. 단순한 애플리케이션에서는 이 패턴만으로도 효과적인 상태 관리가 가능하지만, 규모가 커지면 Context API나 Redux 같은 추가적인 도구를 고려해야 한다.