๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Front-end/Redux

Redux Action๊ณผ Reducer์™€ Store์˜ ๊ด€๊ณ„์„ฑ

by ciocio 2021. 9. 28.

๐Ÿ“Œ  ํŒŒ์ผ ๊ตฌ์กฐ

 

 

 

๐Ÿ“Œ  actions/index.js

 

// action types
export const ADD_TO_CART = "ADD_TO_CART";
export const REMOVE_FROM_CART = "REMOVE_FROM_CART";

// actions creator functions
export const addToCart = (itemId) => {
  return {
    type: ADD_TO_CART,
    payload: {
      quantity: 1,
      itemId
    }
  }
}

export const removeFromCart = (itemId) => {
  return {
    type: REMOVE_FROM_CART,
    payload: {
      itemId
    }
  }
}

 

reducer ํ•จ์ˆ˜์— ์ธ์ž๋กœ ์ „ํ•ด์ฃผ๊ณ ์‹ถ์€ action๋“ค์ด ๊ฐ€๋“๊ฐ€๋“ ๋“ค์–ด์žˆ๋‹ค.

Redux๋ฅผ ์“ฐ๊ธฐ ์ „, props๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚ด๋ ค๋ณด๋‚ด๊ณ  ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ์ƒํƒœ ๋ณ€๊ฒฝ ํ›„ ๋Œ์–ด์˜ฌ๋ ค ์—…๋ฐ์ดํŠธํ•˜๋˜ ๊ทธ ์‹œ์ ˆ์— !

์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ์‹œ์ผœ์ฃผ๋Š” ํ•จ์ˆ˜์˜ "์ธ์ž"๋“ค์„ ์˜ˆ์˜๊ฒŒ ์ •๋ฆฌํ•ด๋†“์€ ๊ฑฐ๋ผ๊ณ  ๋ณด๋ฉด ๋˜๊ฒ ๋‹ค.

 

์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ ์‹œ์ผœ์ค„ ๋•Œ๋Š”

๊ธฐ์กด์˜ ๋ฐ์ดํ„ฐ ํ˜•์‹์— ๋งž์ถ˜ ์ธ์ž๋ฅผ ์ „๋‹ฌํ•ด์•ผํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ธ์ž ์ž์ฒด๋„ ๊ตฌ์กฐ๊ฐ€ ๋ณต์žกํ–ˆ๋‹ค. ๊ธฐ์–ตํ•˜์ง€ ?

ex. { name: lee, age: 20, height: 190 } => ์ด ์ž์ฒด๋ฅผ ์ „๋‹ฌํ•ด์„œ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ ์‹œ์ผœ์ฃผ๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•˜๋‹ค๊ตฌ !!

 

action์€ "๋ฌด์—‡์„"๋งŒ ์ •์˜ํ•ด์ค€๋‹ค. "์–ด๋–ป๊ฒŒ"์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ ๊ณผ์ •์€ ํฌํ•จํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ์ด๋‹ค. (type๊ณผ payload๋งŒ ๐Ÿ˜Š)

์ˆœ์ˆ˜ํ•œ JSON ํ˜•ํƒœ์˜ ๊ฐ์ฒด์—ฌ์•ผ์ง€๋งŒ ์ถ”ํ›„์— History ๊ด€๋ฆฌ๊ฐ€ ํŽธํ•˜๊ณ , ์‰ฝ๊ฒŒ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 


๐Ÿ“Œ  reducers/index.js

 

import { combineReducers } from 'redux';
// itemReducer์™€ notificationReducer ๋ชจ๋‘ reducer ํ•จ์ˆ˜๋‹ค ๐Ÿ˜Š
import itemReducer from './itemReducer';
import notificationReducer from './notificationReducer';

const rootReducer = combineReducers({
  itemReducer,
  notificationReducer
});

export default rootReducer;

 

โ—พ  combineReducers

 

์„œ๋กœ ๋‹ค๋ฅธ Reducing ํ•จ์ˆ˜๋“ค์„ ๊ฐ’์œผ๋กœ ๊ฐ€์ง€๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„์„œ  =>  { itemReducer, notificationReducer }

{ itemReducer, notificationReducer }  == { itemReducer : itemReducer, notificationReducer : notificationReducer }

createStore์— ์ธ์ž๋กœ ๋„˜๊ธธ ์ˆ˜ ์žˆ๋Š” ํ•˜๋‚˜์˜ Reducing ํ•จ์ˆ˜๋กœ ๋ฐ”๊ฟ”์ค€๋‹ค. ์ฆ‰, Function์„ ๋ฆฌํ„ดํ•œ๋‹ค.

โ€ป combineReducers๋Š” ๋ฆฌ๋“€์„œ ๊ณ„์ธต์˜ ์ตœ์ƒ์œ„๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์–ด๋Š ๋‹จ๊ณ„์—์„œ๋“  ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค !

โ€ป code ๋ณต์žกํ•ด์ง€๋ฉด ๋งˆ์Œ๊ป ๋ถ„๋ฆฌํ•˜๊ณ  ๋งˆ์Œ๊ป ํ•ฉ์น˜์‹œ๊ธธ ^!^

 

 

๐Ÿ“Œ  reducers/itemReducer.js

 

import { REMOVE_FROM_CART, ADD_TO_CART } from "../actions/index";
import { initialState } from "./initialState";

const itemReducer = (state = initialState, action) => {

  switch (action.type) {
  
    case ADD_TO_CART:
    
      let copyState1 = Object.assign({}, state);
      copyState1.cartItems = state.cartItems.concat(action.payload);
      return copyState1;
      
    case REMOVE_FROM_CART:
    
      let copyState2 = Object.assign({}, state);
      copyState2.cartItems = state.cartItems.filter((el) => el.itemId !== action.payload.itemId);
      return copyState2;
      
    default:
      return state;
      
  }
}

export default itemReducer;

 

reducer๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ์ง€์ผœ์•ผํ•˜๋Š” ๊ทœ์น™์ด ์žˆ์–ด์„œ ์ž ๊น ์งš๊ณ  ๋„˜์–ด๊ฐ€๋ คํ•œ๋‹ค.

โœ”  ๋ชจ๋“  ์ƒํƒœ๊ฐ€ undefined์ผ๋•Œ๋Š” ์ฒซ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ฃผ์–ด์ง„ state๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผํ•œ๋‹ค.

โœ”  reducer๋Š” undefined๋ฅผ ๋ฐ˜ํ™˜ํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค.  =>  undefined ๋ฐ˜ํ™˜๋˜๋ฉด ์—๋Ÿฌ๋‚œ๋‹ค.

โœ”  state๊ฐ€ undefined๋กœ ์ฃผ์–ด์กŒ์„ ๋•Œ๋„ undefined ๋ฐ˜ํ™˜ X ํ•ด๋‹น reducer์˜ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผํ•œ๋‹ค.

=>  Redux.createStore( combineReducers(...), initialState )๋ฅผ ์‚ฌ์šฉํ•ด ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ๋ช…์‹œํ•˜๋”๋ผ๋„

reducer์— undefined๋ฅผ ์ „๋‹ฌํ•ด์„œ ์ง์ ‘ ํ™•์ธํ•œ๋‹ค๊ณ  ... ๋ฆฌ๋•์Šคใ„ทใ„ทใ„ท

 

 

๐Ÿ“Œ  reducers/initialState.js

 

export const initialState = {
  "items" : [
    {
      "id": 1,
      "name": "2021 ๋‹ฌ๋ ฅ",
      "price": 10000
    },
    {
      "id": 2,
      "name": "์Šˆ๊ฐ€์Šˆ๊ฐ€๋ฃฌ ์Šˆ์ฆˆ",
      "price": 35000
    }
  ],
  "cartItems" : [
    {
      "itemId": 1,
      "quantity": 3
    },
    {
      "itemId": 2,
      "quantity": 10
    }
  ]
}

 

์ƒํƒœ์˜ ์ดˆ๊ธฐ ์ •๋ณด๊ฐ€ reducer ํ•จ์ˆ˜๋“ค๊ณผ ํ•จ๊ป˜ ์žˆ๋”๋ผ. ๋ฐ์ดํ„ฐ์˜ ์œ„์น˜๊ฐ€ ์‹ ๊ธฐํ•ด์„œ ใ…Žใ…Ž ๊ฐ€์ ธ์™€๋ดค๋‹ค.

๊ฐœ๋…์ ์œผ๋กœ๋ณด๋ฉด store์— ์ดˆ๊ธฐ์ •๋ณด๊ฐ€ ๋“ค์–ด์žˆ์„๊ฒƒ๊ฐ™์•˜๋‹ค.

์ •์ž‘ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋Š” ๊ฑด reducer ํ•จ์ˆ˜๋“ค์ด๋‹ˆ๊นŒ !! ๊ธฐ์–ตํ•˜์Ÿˆ ๐Ÿ˜Š

์•ž์„œ ๋งํ–ˆ๋“ฏ์ด createStore( )ํ•จ์ˆ˜๊ฐ€ undefined๋ฅผ ๋ณด๋‚ด์„œ reducer๊ฐ€ ์ดˆ๊ธฐ State๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋ฐ˜ํ™˜ํ•˜๋Š”์ง€ ์•ˆํ•˜๋Š”์ง€ ํ™•์ธ๋„ ํ•œ๋‹ค๊ณ ํ–ˆ์œผ๋‹ˆ๊นŒ, store์— ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฑด ํฐ ์˜๋ฏธ๊ฐ€ ์—†๋Š” ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ์œ ์ถ”ํ•ด๋ณธ๋‹ค. reducer์™€ ์ดˆ๊ธฐ๋ฐ์ดํ„ฐ๋Š” ์ง๊ถ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ด์•ผ๊ฒ ๋‹ค

 

reducer๋Š” ๋ฐ์ดํ„ฐ์˜ ์›๋ณธ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณผ์ •์„ ํ•œ ๋ˆˆ์— ๋ณผ ์ˆ˜ ์žˆ๋Š” ์ค‘์š”ํ•œ ์œ„์น˜์ด๋‹ค.

 


๐Ÿ“Œ  store/store.js

 

import { compose, createStore, applyMiddleware } from "redux";
import rootReducer from '../reducers/index';
import thunk from "redux-thunk";

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? 
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
  
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));

export default store;

 

๐Ÿ“  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__

ํฌ๋กฌ ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ์— ์ž‘์„ฑ๋˜์–ด ์žˆ๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํ•จ์ˆ˜ (๋ฆฌ๋•์Šค ๊ฐœ๋ฐœ์ž ๋„๊ตฌ)

 

โ—พ  compose(...functions)

 

ํ•จ์ˆ˜๋ฅผ ์˜ค๋ฅธ์ชฝ์—์„œ ์™ผ์ชฝ์œผ๋กœ ์กฐํ•ฉํ•˜๋Š” ์—ญํ• .

 

โ—พ  createStore(reducer, [preloadedState])

 

Redux Store(์ƒํƒœ ํŠธ๋ฆฌ ์ „์ฒด๊ฐ€ ๋ณด๊ด€๋˜์–ด ์žˆ๋Š” ๊ณณ)๋ฅผ ์ƒ์„ฑ. 1๊ฐœ์˜ ์•ฑ์— 1๊ฐœ์˜ ์Šคํ† ์–ด โญ

 

โœ”  reducer
(Function) Reducing function์œผ๋กœ, ๋ณ€ํ™”๋œ ์ƒํƒœ ํŠธ๋ฆฌ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
โœ”  [ preloadedState ]
์ดˆ๊ธฐ์ƒํƒœ๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.
์œ ๋‹ˆ๋ฒ„์…œ ์•ฑ์˜ ์„œ๋ฒ„๋‚˜ ์ด์ „์˜ ์ง๋ ฌํ™”๋œ ์‚ฌ์šฉ์ž ์„ธ์…˜์—์„œ "์ƒํƒœ๋ฅผ ์ฑ„์šฐ๊ธฐ ์œ„ํ•ด" ์„ ํƒ์ ์œผ๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
โœ”  [ enhancer ]
Store Enhancer๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค. (applyMiddleware( ) ๋ฐ–์— ์—†์Œ ^_^)
middleware, third-party feature (time travel , persistance) ๊ธฐ๋Šฅ์„ Store์— ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

โ—พ  applyMiddleware(...middlewares ๋ฏธ๋“ค์›จ์–ด๋“ค)

 

Redux์— Middleware(์ž„์˜์˜ ํ•จ์ˆ˜)๋ฅผ ๋”ํ•ด ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๋Š” ๊ฒƒ์„ ๋„์™€์ค€๋‹ค.

์ถ”๊ฐ€๋œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ฏธ๋“ค์›จ์–ด๋Š” (๋ฏธ๋“ค์›จ์–ด ์ฒด์ธ ๋‚ด์—์„œ) ์„œ๋กœ์˜ ๋™์ž‘์— ์˜ํ–ฅ์„ ๋ผ์น˜์ง€ ์•Š๋Š”๋‹ค.

 

๊ฐ๊ฐ์˜ ๋ฏธ๋“ค์›จ์–ด๋Š” Store์˜ dispatch์™€ getState ํ•จ์ˆ˜๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›์•„, ํ•จ์ˆ˜๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์ด ํ•จ์ˆ˜๋Š” ๋ฏธ๋“ค์›จ์–ด์˜ ๋””์ŠคํŒจ์น˜ ํ•จ์ˆ˜์—์„œ next๋กœ ์ฃผ์–ด์ง€๊ณ ,  next๋Š” action์„ ์ธ์ž๋กœ ๋ฐ›์•„ ํ˜ธ์ถœ๋œ๋‹ค.

์ฒด์ธ์˜ ๋งˆ์ง€๋ง‰ ๋ฏธ๋“ค์›จ์–ด๋Š” next์˜ ์ธ์ž๋กœ ์›๋ž˜์˜ dispatch๋ฅผ ๋ฐ›์•„ ์ฒด์ธ์„ ๋งˆ๋ฌด๋ฆฌํ•œ๋‹ค.

์ตœ์ข…์ ์œผ๋กœ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฐ’์€ Store Enhancer๋กœ, ์ฃผ์–ด์ง„ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ ์šฉํ•˜๋Š” ํ•จ์ˆ˜์ด๋‹ค.

๊ฐ€์žฅ ๊ธฐ๋ณธ์˜ ํ˜•ํƒœ : ({ getState, dispatch }) => next => action

 

// ์ฝ”๋“œ ์˜ˆ์‹œ
import { createStore, applyMiddleware } from 'redux';
import memo from './reducers';

function logger({ getState }) {
  return (next) => (action) => {
    console.log('will dispatch', action)
    
    // Call the next dispatch (in the middleware chain)
    let returnValue = next(action);
    
    console.log('state after dispatch', getState())
    
    return returnValue
  }
}

let store = createStore(
  memo,
  [ 'Use Redux' ],
  applyMiddleware(logger)
);

store.dispatch({
  type: 'ADD_MEMO',
  text: 'Understand the middleware'
})
// (These lines will be logged by the middleware:)
// will dispatch: { type: 'ADD_MEMO', text: 'Understand the middleware' }
// state after dispatch: [ 'Use Redux', 'Understand the middleware' ]

 

๐Ÿ“  ์—ฌ๊ธฐ์„œ ์ƒ๊ธฐ๋Š” ์ถ”๊ฐ€ ์งˆ๋ฌธ

๊ทธ๋ ‡๋‹ค๋ฉด, ๋ฏธ๋“ค์›จ์–ด์˜ ์ข…๋ฅ˜์—๋Š” ์–ด๋– ํ•œ ๊ฒƒ๋“ค์ด ์žˆ๋Š”๊ฐ€?

๊ทธ๋ฆฌ๊ณ  ๋ฏธ๋“ค์›จ์–ด๋ž€ ์ •ํ™•์ด ๋ฌด์—‡์ธ๊ฐ€??

๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€