오늘은 저번 포스팅에 이어 Context API를 사용하여 상태를 관리할 수 있도록 리팩토링한 과정을 다뤄보려고 한다.
아래는 기존의 방식으로 상위 컴포넌트에 있는 prop을 하위 컴포넌트로 전달하는 과정이다. prop을 주고 받는 컴포넌트의 사이가 멀수록 prop을 전달하는 코드로 인해 코드가 장황해진다. 이를 prop drilling이라고 한다.
이러한 문제를 context를 사용하면 해결할 수 있다. context를 사용하면 전역적으로 데이터를 공유하기 때문에 데이터가 필요한 컴포넌트에서 아래 그림과 같이 바로 사용 가능하기 때문이다.
이제 이러한 context API를 사용하여 상태를 관리해보자!
createContext
로 CartContext라는 context를 만들어준다.
import { createContext } from 'react';
const CartContext = createContext({
items: [],
totalPrice: 0,
onSave: (selectedItemData) => {},
onAdd: (id) => {},
onRemove: (id) => {}
});
export default CartContext;
이제 value를 만드는데, Provider 컴포넌트 안에 공유하고자 하는 값을 value로 넘겨주면 자식 컴포넌트에서 value의 값에 바로 접근할 수 있다.
const contextValue = {
items: cartState.items,
totalPrice: cartState.totalPrice,
onSave: handleSaveItem,
onAdd: handleAddItem,
onRemove: handleRemoveItem,
};
return (
<CartContext.Provider value={contextValue}>
{children}
</CartContext.Provider>
);
cartContext를 사용하고 싶은 곳에서 useContext
로 context를 사용한다.
예를 들어 하위 컴포넌트인 CartItem 컴포넌트에서 onRemove와 onAdd 함수를 사용한다고 해보자.
완성된 코드는 아래와 같고, 코드 아래에서 더 자세히 알아보자.
// 📃 ToppingItemForm.jsx
import React, { useState, useContext } from 'react';
import CartContext from '../../../store/cart-context';
const ToppingItemForm = ({ topping, onSaveCategories, id }) => {
// ...생략
const cartCtx = useContext(CartContext);
const submitHandler = (e) => {
e.preventDefault();
cartCtx.onSave(itemState);
}
return (
// ...생략
);
};
export default ToppingItemForm;
useContext Hook을 임포트해주고, CartContext도 임포트해준다.
import { useContext } from 'react';
import CartContext from '../../../store/cart-context';
내려받은 prop을 지우고 대신 위에서 임포트한 context인 CartContext에서 값을 읽는다.(cartCtx라고 이름지었다)
const cartCtx = useContext(CartContext);
context를 쓸 수 있도록 Provider
로 제공해야 한다. 기본적인 문법은 아래와 같다. context를 쓸 곳을 Context Provider로 래핑한다. (나는 최상위 컴포넌트인 App에서 리턴하는 부분을 묶었다.)
// 📃 App.jsx
<CartContext.Provider value={contextValue}>
...
</CartContext.Provider>
하지만 파일을 분리하여 아래와 같이 간결하게 쓰기 위해
// 📃 App.jsx
<CartProvider>
...
</CartProvider>
아래처럼 CartProvider를 따로 만들었다.
// 📃 cartProvider.jsx
<CartContext.Provider value={contextValue}>
{children}
</CartContext.Provider>
context를 사용하지 않았다면 App.jsx => Toppings.jsx => AvailableToppings.jsx => ToppingItem.jsx => ToppingItemForm.jsx
이렇게 네 단계에 걸쳐 onRemove와 onAdd prop을 전달해야 했을 것이다.
하지만 context API를 사용함으로써 App.jsx=>ToppingItemForm.jsx
로 바로 전달할 수 있다.
리팩토링만 했으므로 화면에서 보이는 내용은 바뀌지 않았다!
Context API를 사용하면 유지보수가 어렵다는 단점이 있지만, 아직 유지보수를 해보지 않았기 때문에 단점을 체감하지 못했다. 그래서 앞으로 진행하면서 어려움을 느껴보고 싶다.
그리고 Context API처럼 상태를 관리할 수 있는 Redux와 같은 상태관리 라이브러리도 배우고 싶다!
안녕하세요… 혹시 햄스터 좋아한다고 남겼던 사람이라고 하면 기억하시려나요 ㅎ.ㅎ .. 댓글을 남겨두 되는지 모르겠지만 답변 보고 댓글 남겨봐요 .!!