리액트에서 컨텍스트(Context)와 리듀서(Reducer)를 사용하는 방법
import React, { createContext, useReducer } from 'react'; // 카운터 컨텍스트를 생성합니다. 초기 상태는 0입니다. const CounterContext = createContext({ count: 0, increment: () => {}, decrement: () => {}, }); // 카운터 상태를 관리하는 리듀서 함수입니다. function counterReducer(state, action) { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; default: return state; } } // CounterContextProvider 컴포넌트는 카운터 상태를 관리하고, 자식 컴포넌트에게 카운터 컨텍스트를 제공합니다. export function CounterContextProvider({ children }) { const [state, dispatch] = useReducer(counterReducer, { count: 0 }); // 카운터를 증가시키는 함수입니다. function increment() { dispatch({ type: 'INCREMENT' }); } // 카운터를 감소시키는 함수입니다. function decrement() { dispatch({ type: 'DECREMENT' }); } const counterContext = { count: state.count, increment, decrement, }; // CounterContext.Provider 컴포넌트를 반환합니다. value prop에는 카운터 컨텍스트를 전달합니다. return ( <CounterContext.Provider value={counterContext}> {children} </CounterContext.Provider> ); } export default CounterContext;
위 코드는 카운터 애플리케이션을 구현한 것입니다. 카운터의 상태를 증가시키거나 감소시키는 기능을 제공합니다. 이 컨텍스트는 카운터 상태를 관리하고, 해당 상태를 필요로 하는 컴포넌트에게 제공합니다. 이를 통해 상태 관리를 중앙에서 할 수 있으며, 상태를 필요로 하는 여러 컴포넌트가 재렌더링 없이 상태를 공유할 수 있습니다. 이 방식은 상태 관리 라이브러리인 Redux의 기본 개념과 유사합니다.
컨텍스트(Context)는 React 컴포넌트 트리 안에서 전역적(global)이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법입니다. 컨텍스트를 사용하면 중간에 있는 엘리먼트들을 통해 props를 넘겨주지 않고도 컴포넌트 트리 내에서 하위 컴포넌트들이 값을 사용할 수 있습니다.
리듀서(Reducer)는 현재의 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 함수입니다. 리듀서 함수에서 새로운 상태를 만들 때는 반드시 불변성을 지켜주어야 합니다. 이전 상태는 절대로 건드리지 않고, 변화를 줄 상태의 사본을 만들어서 그 사본에 변화를 주고, 그 사본의 상태를 새로운 상태로 설정해주어야 합니다. 이를 통해 상태가 언제, 왜, 어떻게 업데이트되었는지 쉽게 추적하고 이해할 수 있습니다. 또한, 성능 최적화에도 큰 도움이 됩니다.
이런 방식으로 컨텍스트와 리듀서를 사용하면 상태 관리 로직을 컴포넌트 외부에 작성할 수 있고, 여러 컴포넌트에서 상태를 공유하거나 상태를 조작하는 함수를 재사용할 수 있습니다. 이는 코드의 재사용성을 높이고, 유지 보수를 용이하게 합니다. 또한, 상태 관리 로직을 분리함으로써 컴포넌트의 역할을 명확하게 하고, 가독성을 높일 수 있습니다.
이렇게 컨텍스트와 리듀서를 사용하면 상태 관리를 효과적으로 할 수 있습니다. 이는 큰 규모의 프로젝트에서 특히 유용합니다. 하지만 작은 규모의 프로젝트에서는 굳이 이런 방식을 사용하지 않아도 됩니다. 상태를 props로 넘겨주는 것만으로도 충분히 상태 관리를 할 수 있습니다. 하지만 프로젝트의 규모가 커지면 커질수록 상태 관리가 복잡해지므로, 이런 방식을 사용하는 것이 좋습니다.
장바구니 예
1. CartContext.jsx
import { createContext, useReducer } from "react" // 장바구니 컨텍스트를 생성합니다. 초기 상태는 빈 아이템 배열입니다. const CartContext=createContext({ items:[], addItem:(item)=>{}, removeItem:(id)=>{}, }); // 장바구니 상태를 관리하는 리듀서 함수입니다. function cartReducer(state, action){ if(action.type==='ADD_ITEM'){ // 기존 장바구니에 동일한 상품이 있는지 확인합니다. const existingCartItemIndex=state.items.findIndex((item)=>item.id===action.item.id); // 장바구니 아이템 배열을 복사합니다. const updatedItems=[...state.items]; if(existingCartItemIndex >-1){ // 장바구니에 동일한 상품이 있으면, 해당 상품의 수량을 1 증가시킵니다. const existingItem=state.items[existingCartItemIndex]; const updateItem={ ...existingItem, quantity:existingItem.quantity+1, }; updatedItems[existingCartItemIndex]=updateItem; }else{ // 장바구니에 동일한 상품이 없으면, 새로운 상품을 추가합니다. updatedItems.push({...action.item,quantity:1}); } return { ...state,items:updatedItems} } if(action.type==='REMOVE_ITEM'){ // 삭제할 상품의 인덱스 번호를 가져옵니다. const existingCartItemIndex=state.items.findIndex((item)=>item.id===action.id); const existingCartItem=state.items[existingCartItemIndex]; const updatedItems=[...state.items]; if(existingCartItem.quantity===1){ // 상품의 수량이 1개면, 해당 상품을 삭제합니다. updatedItems.splice(existingCartItemIndex, 1); }else{ // 상품의 수량이 1개 이상이면, 상품의 수량을 1개 줄입니다. const updatedItem={ ...existingCartItem, quantity:existingCartItem.quantity-1, } updatedItems[existingCartItemIndex]=updatedItem; } return { ...state,items:updatedItems} } return state; } // CartContextProvider 컴포넌트는 장바구니 상태를 관리하고, 자식 컴포넌트에게 장바구니 컨텍스트를 제공합니다. export function CartContextProvider({children}){ const [cart, dispatchCartAction] =useReducer(cartReducer,{items:[]}); // 상품을 장바구니에 추가하는 함수입니다. function addItem(item){ dispatchCartAction({type:'ADD_ITEM',item}); } // 장바구니에서 상품을 제거하는 함수입니다. function removeItem(id){ dispatchCartAction({type:'REMOVE_ITEM',id}); } const cartContext={ items:cart.items, addItem, removeItem } console.log(cartContext); // CartContext.Provider 컴포넌트를 반환합니다. value prop에는 장바구니 컨텍스트를 전달합니다. return( <CartContext.Provider value={cartContext}> {children} </CartContext.Provider> ) } // CartContext를 export 합니다. 다른 컴포넌트에서 이 컨텍스트를 사용할 수 있습니다. export default CartContext;
위 코드는 React와 useReducer 훅을 사용하여 장바구니 기능을 구현한 것입니다. 장바구니에 상품을 추가하거나 제거하는 기능을 제공합니다.
이 컨텍스트는 장바구니 상태를 관리하고, 해당 상태를 필요로 하는 컴포넌트에게 제공합니다. 이를 통해 상태 관리를 중앙에서 할 수 있으며,
상태를 필요로 하는 여러 컴포넌트가 재렌더링 없이 상태를 공유할 수 있습니다. 이 방식은 상태 관리 라이브러리인 Redux의 기본 개념과 유사합니다.
2. 사용방법
1)설정 <CartContextProvider> 로 감싸준다.
import Header from "./components/Header"; import Meals from "./components/Meals"; import { CartContextProvider } from "./store/CartContext"; function App() { return ( <CartContextProvider> <Header /> <Meals /> </CartContextProvider> ); } export default App;
2). useContext
const cartCtx=useContext(CartContext);
import { useContext } from "react"; import { currencyFormatterKR } from "../utils/formatting"; import Button from "./Button"; import CartContext from "../store/CartContext"; export default function MealItem({meal}) { const cartCtx=useContext(CartContext); function handleAddMealToCart(){ cartCtx.addItem(meal); } return ( <li> <p> <Button onClick={handleAddMealToCart} >장바구니 추가</Button> </p> </li> ) }
3) 장바구니 전체 수량
import { useContext } from 'react'; import logImg from '../assets/logo.jpg'; import Button from './Button'; import CartContext from '../store/CartContext'; export default function Header() { const cartCtx=useContext(CartContext); //reduce 의 첫번째 인자값은 총항목을 표시 const totalCartItems =cartCtx.items.reduce((totalNumberOfItems, item)=>{ return totalNumberOfItems+item.quantity; }, 0); return ( <header id="main-header"> <div id="title"> <img src={logImg} /> <h1>리액트 푸드</h1> </div> <nav> <Button textOnly >장바구니(0)</Button> </nav> </header> ) }
소스 :
https://github.com/braverokmc79/macaronics-react-food
댓글 ( 0)
댓글 남기기