관리 메뉴

Silver Library (Archived)

react context 를 react-redux 로 변환하기 - 1 본문

카테고리 없음

react context 를 react-redux 로 변환하기 - 1

Ayin Kim 2022. 12. 24. 22:53
반응형

개요.

적어도 지금의 이해도로 판단하기에, 필자는 react context hook 의 개념만큼은 react-redux 와 상당부분 공유가 가능하다고 믿고 있다.

 

그나저나, 이걸 어떻게 변환할까?

우선 필자의 경우, 그런 context 용으로 코드를 구성해 둔 파일이 있다. 아마 당신도 그랬듯이.

적어도 redux 를 떠올렸을 때 바로 든 생각이 저 react의 context hook 였으므로,

우선은 Redux 를 도입하려는 목적의 근원지인 context 구성 파일을 대상으로 실험해보았다.

 

한계와 주의사항.

react-redux 도 엄연히 공식 문서에 언급은 되어 있으나, Redux 측에서 공식적으로 권장하는 방식은 Redux Toolkit 으로 시작하는 것이라고 한다. 만약 공식대로 Redux Toolkit 으로 한다면:

# NPM
npm install @reduxjs/toolkit

으로 설치 후, 공식 문서에 적힌대로 갈피를 잡아 가는 것으로 설정.

 

우선 큰 그림을 먼저 그리자.

 

Getting Started with Redux | Redux

Introduction > Getting Started: Resources to get started learning and using Redux

redux.js.org

 

아래는 문제의 기존 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

 

Why Redux Toolkit is How To Use Redux Today | Redux

Introduction > Why RTK is Redux Today: details on how RTK replaces the Redux core

ko.redux.js.org

https://ko.redux.js.org/introduction/examples

 

예제 | Redux

소개 > 예제: Redux 인터렉티브 예제

ko.redux.js.org

https://ko.redux.js.org/introduction/core-concepts

 

Core Concepts | Redux

Introduction > Core Concepts: A quick overview of Redux's key idea, reducer functions

ko.redux.js.org

https://blog.isquaredsoftware.com/2018/11/react-redux-history-implementation/

 

Idiomatic Redux: The History and Implementation of React-Redux

Some history and explanations of how React-Redux got its API, and how it works internally

blog.isquaredsoftware.com

https://ko.redux.js.org/tutorials/index

 

Redux 튜토리얼 색인 | Redux

Overview of the Redux tutorial pages

ko.redux.js.org

 

 

Redux 핵심, Part 1: Redux Overview and Concepts | Redux

The official Essentials tutorial for Redux: learn how to use Redux, the right way

ko.redux.js.org

 

이하, 'Redux 핵심' 카테고리의 하위 카테고리 참고. (이번의 경우, RTK query 는 제외).

 

그리고 이걸 한번더 보는걸 추천. 복습용으로 추천.

https://ko.redux.js.org/tutorials/fundamentals/part-1-overview

 

Redux 기반, Part 1: Redux Overview | Redux

The official Fundamentals tutorial for Redux: learn the fundamentals of using Redux

ko.redux.js.org

헷깔리면 여기도 추천.

https://ko.redux.js.org/usage/

 

Usage Guides Index | Redux

The Usage Guides section provides practical guidance on how to correctly use Redux in real-world applications, including project setup and architecture, patterns, practices, and techniques.

ko.redux.js.org

reducer structure

https://ko.redux.js.org/usage/structuring-reducers/basic-reducer-structure

 

기본 리듀서 구조 | Redux

리듀서 구조 잡기 > 기본 리듀서 구조: Overview of how reducer functions work with Redux state

ko.redux.js.org