App 컴포넌트 밑으로 다들 어떤 context를 사용하고 싶어.
그러면 index.js에서 App 컴포넌트를 context provider로 감싸주면 되겠지?
//OrderContext.js
export const OrderContext = createContext();
//index.js
<OrderContext.Provider value={value}>
<App />
</OrderContext.Provider>
근데 실제로 프로젝트를 하거나 어떤 앱을 만들 때, 전역적으로 관리할 값이 한두개가 아닐거란 말이지. 실제 사용할 데이터도 들어가고, 데이터를 업데이트하는 함수도 들어가고...
그럼 value에 들어갈 값이 겁나 많아지는데 이걸 그냥 index.js에서 쭉 써버릴거야?
❌ 로직이 복잡하고, 컴포넌트그러면 코드가 더러워지고,
그래서 context 폴더를 아예 따로 만들고 context와 관련된 것은 모두 해당 폴더와 컴포넌트들에서 생성해서 export해서 사용할거야.
//Context 폴더 > OrderContext.js
export const OrderContext = createContext();
export function OrderContextProvider(props) {
return <OrderContext.Provider value={value} {...props} />
}
//index.js
import {OrderContextProvider} from 'OrderContext.js';
<OrderContextProvider>
<App />
</OrderContextProvider>
그럼 여기서 오잉또잉 모먼트 발생 🧐
OrderContextProvider 함수를 만드는데 인수로 props는 왜 넣고, {...props} 는 또 뭐냐?
➡️ 이건 어떤 이유는 없고 그냥 리액트에서 그렇게 정한거임. 그냥 그런줄 알자.
<OrderContext.Provider> 얘가 감싸고 있는 내용 = <App /> = props.children
그런 이유에서 아래 둘은 같은 코드임
<OrderContext.Provider value={value} {...props} />
<OrderContext.Provider value={value}>
{props.children}
</OrderContext.Provider>
export function OrderContextProvider(props) {
const [orderCounts, setOrderCounts] = useState({
products: new Map(),
options: new Map(),
});
const value = useMemo(() => {
return [{ ...orderCounts }];
}, [orderCounts]);
return <OrderContext.Provider value={value} {...props} />;
}
new Map()
키-값 쌍인 요소를 가진 배열을 만들 수 있는 생성자(Constructor)
useMemo()
value가 업데이트되면 해당 value를 사용하고 있는 다른 모든 컴포넌트들이 리렌더링됨. 그때마다 리렌더링되는 것을 방지하기 위해 value의 값을 메모이제이션하려고 사용한다.
orderCounts가 바뀌면 당연히 메모이제이션한 값은 버리고 새로운 값을 사용해야하기 때문에 의존성 배열에 orderCounts를 넣어준다.
value의 리턴 값이 배열인 이유
orderCounts만 리턴할거라면 그냥 쓰면되는데 그 외에 다른 것들도 value에 넣어 다른 컴포넌트들에 전달해줘야하기 때문에 배열의 형태로 작성한다.
그리고 orderCounts에 spread operator 사용하는 것도 같은 목적으로...
const updateItemCount = (itemName, newItemCount, orderType) => {
//products, options 중에 해당하는 orderType의 배열을 깊은 복사 (불변성 위해)
const oldOrderMap = orderCounts[orderType];
const newOrderMap = new Map(oldOrderMap);
newOrderMap.set(itemName, parseInt(newItemCount));
const NewOrderCounts = { ...orderCounts };
NewOrderCounts[orderType] = newOrderMap;
setOrderCounts(NewOrderCounts);
};
불변성
리액트의 불변성을 위해 state를 바로 변경하지 않고 복사한 후, 복사한 값에 변화를 주고 그 값을 setter를 통해 실제 state에 반영한다.
여러 번 복사하고, set하고.. 하는 이유가 모두 불변성을 위함이다.
그리고 여기서는 orderCounts 라는 state가 가진 값이 2개의 depth를 갖고 있기 때문에 복사도 2번, set도 2번 해준다.
parseInt()
newItemCount 값이 숫자로 저장되어야 하는데 혹여나 string이 들어올 수도 있으니까 number로 설정하는 작업을 추가한다.
이제 렌더링되는 실제 컴포넌트에 가서 context value를 사용하면 된다.
const Type = ({ orderType }) => {
const [orderData, updateItemCount] = useContext(OrderContext);
return (
<div>
<h2>상품 종류</h2>
<p>상품 하나의 가격</p>
<p>총 가격: {orderData.totals[orderType]}</p>
</div>
)
}
useContext 훅을 통해 만들어 놓은 context를 불러오고, 필요한 곳에 가서 해당 값을 사용해주면 된다!