카테고리 없음

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

Eppo:) 2025. 4. 13. 17:50

리프팅 스테이트 업은 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 같은 추가적인 도구를 고려해야 한다.