Silver Library (Archived)
react context 를 react-redux 로 변환하기 - 1 본문
개요.
적어도 지금의 이해도로 판단하기에, 필자는 react context hook 의 개념만큼은 react-redux 와 상당부분 공유가 가능하다고 믿고 있다.
그나저나, 이걸 어떻게 변환할까?
우선 필자의 경우, 그런 context 용으로 코드를 구성해 둔 파일이 있다. 아마 당신도 그랬듯이.
적어도 redux 를 떠올렸을 때 바로 든 생각이 저 react의 context hook 였으므로,
우선은 Redux 를 도입하려는 목적의 근원지인 context 구성 파일을 대상으로 실험해보았다.
한계와 주의사항.
react-redux 도 엄연히 공식 문서에 언급은 되어 있으나, Redux 측에서 공식적으로 권장하는 방식은 Redux Toolkit 으로 시작하는 것이라고 한다. 만약 공식대로 Redux Toolkit 으로 한다면:
# NPM
npm install @reduxjs/toolkit
으로 설치 후, 공식 문서에 적힌대로 갈피를 잡아 가는 것으로 설정.
우선 큰 그림을 먼저 그리자.
아래는 문제의 기존 react 프로젝트 내부에 있던 context 구성 코드 (Before).
import React, { createContext, useState } from "react";
import { ItemsList } from "../ItemsList";
export const DataContext = createContext(null);
const getDefaultCart = () => {
let cart = {};
for (let i = 0; i <= ItemsList.length; i++) {
cart[i] = 0;
}
return cart;
};
export const DataContextProvider = (props) => {
const [cartItems, setCartItems] = useState(getDefaultCart());
const checkoutTotalSum = () => {
let totalAmount = 0;
for (const item in cartItems) {
if (cartItems[item] > 0) {
let itemInfo = ItemsList.find((product) => product.id === Number(item));
totalAmount += cartItems[item] * itemInfo.price;
}
}
return totalAmount;
};
const addItemToCart = (itemId) => {
setCartItems((prev) => ({ ...prev, [itemId]: prev[itemId] + 1 }));
};
const removeItemFromCart = (itemId) => {
setCartItems((prev) => ({ ...prev, [itemId]: prev[itemId] - 1 }));
};
const updateCartItemCount = (newAmount, itemId) => {
setCartItems((prev) => ({ ...prev, [itemId]: newAmount }));
};
// Always check!
const contextValue = {
cartItems,
addItemToCart,
removeItemFromCart,
updateCartItemCount,
checkoutTotalSum,
// checkout,
};
// console.log(cartItems);
return (
<DataContext.Provider value={contextValue}>
{props.children}
</DataContext.Provider>
);
};
react-redux 로 변환 후의 코드 구성 (After).
import { legacy_createStore as createStore } from 'redux';
import { Reducer } from 'redux'
import { Provider, useSelector, useDispatch } from 'react-redux';
// reducer function 을 생성. 이는 각 case action 별, 거점 역할을 하게 됨.
const reducer = (state, action) => {
switch (action.type) {
// redux 에서는 case 뒤에 case 명을 camel case 가 아닌 다음과 같이 작성. 규칙으로 보임.
case 'ADD_ITEM_TO_CART':
return {
...state,
cartItems: {
...state.cartItems,
// 여기서 action.payload 는 해당 액션과 같이 전달하는 값을 의미.
// payload 자체가 추가 정보 내용을 전달하는 목적이 큼.
[action.payload]: state.cartItems[action.payload] + 1
}
};
case 'REMOVE_ITEM_FROM_CART':
return {
...state,
cartItems: {
...state.cartItems,
[action.payload]: state.cartItems[action.payload] - 1
}
};
case 'UPDATE_CART_ITEM_COUNT':
return {
...state,
cartItems: {
...state.cartItems,
[action.payload.itemId]: action.payload.newAmount
}
};
default:
return state;
}
};
// store 생성 후, reducer function 으로 패스.
const store = createStore(reducer, { cartItems: getDefaultCart() });
// 기존의 useState 를 useDispatch 로 대체.
const DataContextProvider = (props) => {
const dispatch = useDispatch();
const checkoutTotalSum = () => {
let totalAmount = 0;
const cartItems = useSelector(state => state.cartItems);
for (const item in cartItems) {
if (cartItems[item] > 0) {
let itemInfo = ItemsList.find((product) => product.id === Number(item));
totalAmount += cartItems[item] * itemInfo.price;
}
}
return totalAmount;
};
const addItemToCart = (itemId) => {
dispatch({ type: 'ADD_ITEM_TO_CART', payload: itemId });
};
const removeItemFromCart = (itemId) => {
dispatch({ type: 'REMOVE_ITEM_FROM_CART', payload: itemId });
};
const updateCartItemCount = (newAmount, itemId) => {
dispatch({ type: 'UPDATE_CART_ITEM_COUNT', payload: { newAmount, itemId } });
};
// DataContext.Provider 를 redux 에서 제공하는 Provider 로 변경.
return (
// 여기서 store 는 props 형태로 패스.
<Provider store={store}>
{props.children}
</Provider>
)};
주의: 이 코드는 아직 예정대로 typescript 로의 변환 작업을 마치지 않은 상태.
나머지 컴포넌트들을 수정 할 때에는 react-redux 내에서 제공하는 API hook 인 useSelector, useDispatch 를 사용하여, context 와 유사하게. 유의할 점이 있다면, 이것은 redux 인 만큼, 다른 레벨링으로도 엑세스와 달리, global state 로써 상태값을 자유롭게 반환함을 의미하며, action 의 경우, useDispatch 로 보냄으로써 진행.
지금까지 한 내역을 정리해보면:
createStore 와 Provider 컴포넌트를 redux 라이브러리에서 도입 후,
useSelector 와 useDispatch 는 react-redux 라이브러리에서 도입 한 것.
reducer 함수는 각 action 들에 각기 대응하고자 생성 한 것.
이 후, 상태 업데이트가 이뤄진 각기의 action 들을 return 함.
useState 는 useDispatch 로 대응 가능하며, 기존에 있던 상태 변형(mutation of its state) 성격의 함수들은 action 을 dispatch 하는 형태로 변경.
Provider 로 wrapping 후, store 에 props 형태로 pass.
이 후, 공식에서 언급 된 대로 useSelector 및 useDispatch 를 써가면서 조정 해 나가면 될 듯.
메모.
지금 next.js 와 redux 도입을 실험중인데, 실험만 성공적이라면 생각보다 빨리 끝낼 수 있을지도 모르겠다.
그러나 기초 부문의 이해에 좀 더 무게를 두고, Redux toolkit 내용을 한번 더 보고 이어서 진행 해 볼 예정.
참고.
https://ko.redux.js.org/introduction/why-rtk-is-redux-today
https://ko.redux.js.org/introduction/examples
https://ko.redux.js.org/introduction/core-concepts
https://blog.isquaredsoftware.com/2018/11/react-redux-history-implementation/
https://ko.redux.js.org/tutorials/index
이하, 'Redux 핵심' 카테고리의 하위 카테고리 참고. (이번의 경우, RTK query 는 제외).
그리고 이걸 한번더 보는걸 추천. 복습용으로 추천.
https://ko.redux.js.org/tutorials/fundamentals/part-1-overview
헷깔리면 여기도 추천.
https://ko.redux.js.org/usage/
reducer structure
https://ko.redux.js.org/usage/structuring-reducers/basic-reducer-structure