리액트에서 컨텍스트(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)
댓글 남기기