sliver__

[React] Context API + useReducer 본문

Frontend/React

[React] Context API + useReducer

sliver__ 2025. 4. 6. 18:50
728x90

Context API

여러 컴포넌트 간 상태 공유를 위해 State를 끌어올리게 된다.

그러면 가장 상단인 App 컴포넌트에 상태를 정의하게 되고, 정의한 상태를 props를 통해 사용하려는 컴포넌트까지 전달한다.

 

컴포넌트 관계가 복잡해지고 깊어지면 props를 사용하지도 않는데 넘겨줘야하는 상황이 발생한다.

 

이러한 상황을 prop drilling 이라고 한다.

 

컴포넌트에 계속 prop을 넘겨주기 때문이다.

 

이러한 상황을 해결하기 위해서 context 라는 개념을 사용한다.

 

context는 컴포넌트 전체를 둘러싼 개념이라고 생각하면 된다.

context에 상태를 정의하고 상태를 변경하기 위한 함수를 정의한다.

그리고 해당 상태 변경이 필요한 컴포넌트만 접근하면 된다.

 

그러면 prop으로 상태 값이나 함수를 넘길 필요가 없게 된다.

 

https://deku.posstree.com/ko/react/context-api/

 

[React] Context API

React에서 데이터를 다루는 개념중 하나인 Context API를 사용하는 방법에 대해서 알아봅시다.

deku.posstree.com

 

1. 컨텍스트를 사용하기 위해서는 createContext 함수를 통해 컨텍스트를 생성하고

export const CardContext = createContext({
  items: [],
  addItemToCart: () => {},
  updateItemToCart: () => {},
});

2. useContext를 통해 컨텍스트에 접근한다.

const { items, updateItemToCart } = useContext(CardContext);

 

코드 전체에서 items 배열을 관리할거고 addItemToCart, updateItemToCart 함수를 선언해서 item 배열에 값을 추가하거나 업데이트 할 것이라고 선언한다.

 

선언한 컨텍스트를 컴포넌트가 접근 가능하게 하려면 어떻게 해야할까?

답은 컨텍스트를 사용하려는 가장 바깥 컴포넌트에 선언해주면 된다.

 

function App() {
  return (
    <CardContextProvider>
      <Header />
      <Shop>
        {DUMMY_PRODUCTS.map((product) => (
          <li key={product.id}>
            <Product {...product} />
          </li>
        ))}
      </Shop>
    </CardContextProvider>
  );
}

 

CardContextProvider내부에 Header, Shop, Product 컴포넌트가 있는데 해당 컴포넌트들은 CardContext에 모두 접근이 가능하다.


 

useReducer

context api와 함께 사용되는 개념이 useReducer이다.

 

useReducer는 dispatch라는 개념이 나온다.

 

컨텍스트에서 관리하는 상태 값을 업데이트 하기 위해서 함수를 따로 정의했다.

 

이를 하나의 함수에서 관리한다고 생각하면 된다.

 

그러면 하나의 함수에서 동작에 따른 코드를 관리하기 쉬울 것 같다.

 

function shoppingCartReducer(state, action) {
  if (action.type === "ADD_ITEM") {
    const updatedItems = [...state.items];

    const existingCartItemIndex = updatedItems.findIndex(
      (cartItem) => cartItem.id === action.payload
    );
    const existingCartItem = updatedItems[existingCartItemIndex];

    if (existingCartItem) {
      const updatedItem = {
        ...existingCartItem,
        quantity: existingCartItem.quantity + 1,
      };
      updatedItems[existingCartItemIndex] = updatedItem;
    } else {
      const product = DUMMY_PRODUCTS.find(
        (product) => product.id === action.payload
      );
      updatedItems.push({
        id: action.payload,
        name: product.title,
        price: product.price,
        quantity: 1,
      });
    }

    return {
      ...state,
      items: updatedItems,
    };
  }

  if (state.type === "UPDATE_ITEM") {
    const updatedItems = [...state.items];
    const updatedItemIndex = updatedItems.findIndex(
      (item) => item.id === action.payload.productId
    );

    const updatedItem = {
      ...updatedItems[updatedItemIndex],
    };

    updatedItem.quantity += amount;

    if (updatedItem.quantity <= 0) {
      updatedItems.splice(updatedItemIndex, 1);
    } else {
      updatedItems[updatedItemIndex] = updatedItem;
    }

    return {
      ...state,
      items: updatedItems,
    };
  }
  return state;
}

 

ADD_ITEM, UPDATE_ITEM이라는 type을 통해 동작을 구분하여 수행된다.

 

호출은 다음과 같다.

 

function handleAddItemToCart(id) {
    shoppingCartDispatch({
      type: "ADD_ITEM",
      payload: id,
    });
  }

  function handleUpdateCartItemQuantity(productId, amount) {
    shoppingCartDispatch({
      type: "UPDATE_ITEM",
      payload: {
        productId,
        amount,
      },
    });
  }

 

ShoppingCartDispatch({state, action}) 을 선언하여 shoppingCartReducer 함수를 호출한다.

 

728x90

'Frontend > React' 카테고리의 다른 글

[React] useEffect  (0) 2025.04.11
[React] Context + useReducer  (0) 2025.04.06
[React] useRef props (react 18 vs 19)  (0) 2025.02.16
[React] useRef  (0) 2025.02.16
[React] StrictMode  (0) 2025.02.15
Comments