[TIL] Day51 #Redux #Cmarket Redux

Beanxxยท2022๋…„ 7์›” 6์ผ
1

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
51/120
post-thumbnail

2022.07.06(Wed)

[TIL] Day51
[SEB FE] Day50

โ˜‘๏ธย Redux

โœ‹ย Redux๋Š” React์—†์ด๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

๐Ÿ“Žย Redux ์ƒํƒœ ๊ด€๋ฆฌ ์ˆœ์„œ

  1. ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ ๋ฐœ์ƒ โ†’ ๋ณ€๊ฒฝ๋  ์ƒํƒœ ์ •๋ณด๊ฐ€ ๋‹ด๊ธด Action ๊ฐ์ฒด ์ƒ์„ฑ
  2. Action ๊ฐ์ฒด๊ฐ€ Dispatch ํ•จ์ˆ˜ ์ธ์ž๋กœ ์ „๋‹ฌ
  3. Dispatch ํ•จ์ˆ˜๊ฐ€ Action ๊ฐ์ฒด๋ฅผ Reducer ํ•จ์ˆ˜๋กœ ์ „๋‹ฌ
  4. Reducer ํ•จ์ˆ˜๋Š” Action ๊ฐ์ฒด ๊ฐ’ ํ™•์ธ ํ›„, ๊ฐ’์— ๋”ฐ๋ผ ์ „์—ญ ์ƒํƒœ ์ €์žฅ์†Œ์ธ Store ์ƒํƒœ ๋ณ€๊ฒฝ
  5. ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด, React๋Š” ํ™”๋ฉด ๋ฆฌ๋ Œ๋”๋ง

Action ๐Ÿ‘‰ย Dispatch ๐Ÿ‘‰ย Reducer ๐Ÿ‘‰ย Store

๐Ÿซ ย Redux์—์„  ์œ„์™€ ๊ฐ™์€ ์ˆœ์„œ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹จ๋ฐฉํ–ฅ์œผ๋กœ ํ๋ฆ„

๐ŸŒŠย Action

์–ด๋–ค ์•ก์…˜์„ ์ทจํ•  ๊ฒƒ์ธ์ง€ ์ •์˜ํ•ด ๋†“์€ ๊ฐ์ฒด

// payload ํ•„์š”X
{type: "INCREASE'} // type์€ ๋Œ€๋ฌธ์ž & Snake_Case๋กœ ์ž‘์„ฑํ•˜๊ธฐ!

// payload ํ•„์š”O
{type: "SET_NUMBER', payload: 10} // payload๋กœ ๊ตฌ์ฒด์ ์ธ ๊ฐ’ ์ „๋‹ฌ

โœ‹ย type(ํ•ด๋‹น Action ๊ฐ์ฒด๊ฐ€ ์–ด๋–ค ๋™์ž‘์„ ํ•˜๋Š”์ง€ ๋ช…์‹œ) ํ•„์ˆ˜ ์ง€์ •!

โœ‹ย Action ๊ฐ์ฒด ์ƒ์„ฑ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Œ โ‡’ ์•ก์…˜ ์ƒ์„ฑ์ž(Action Creator)

// [Action Creator]

// payload ํ•„์š”X
const increase = () => {
	return {
		type: 'INCREASE'
	}
}

// payload ํ•„์š”O
const setNumber = (num) => {
	return {
		type: 'SET_NUMBER',
		payload: num
	}
}

๐ŸŒตย Dispatch

Reducer๋กœ Action์„ ์ „๋‹ฌํ•ด์ฃผ๋Š” ํ•จ์ˆ˜

// Action ๊ฐ์ฒด ์ง์ ‘ ์ž‘์„ฑํ•˜๋Š” ๊ฒฝ์šฐ
dispatch({type: 'INCREASE'});
dispatch({type: 'SET_NUMBER', payload: 10});

// Action Creator ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ
dispatch(increase());
dispatch(setNumber(10));

// => Action ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌ๋ฐ›์€ Dispatchํ•จ์ˆ˜๋Š” Reducer ํ˜ธ์ถœ

๐Ÿฅ€ย Reducer

Dispatch์—๊ฒŒ์„œ ์ „๋‹ฌ๋ฐ›์€ Action๊ฐ์ฒด์˜ type๊ฐ’์— ๋”ฐ๋ผ์„œ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ์‹œํ‚ค๋Š” ํ•จ์ˆ˜

const count =1;

const counterReducer = (state = count, action) { // ์ธ์ž๋กœ ์ดˆ๊ธฐ์ƒํƒœ ์„ค์ •
	switch(action.type). // Action๊ฐ์ฒด์˜ type๊ฐ’์— ๋”ฐ๋ฅธ ์กฐ๊ฑด๋ฌธ
		case 'INCREASE':   // action === 'INCREASE'
			return state + 1;
		case 'DECREASE':   // action === 'DECREASE'
			return state - 1;
		case 'SET_NUMBER': // action === 'SET_NUMBER'
			return action.payload;
		default:  // ์œ„์˜ ์กฐ๊ฑด์— ๋ชจ๋‘ ํ•ด๋‹น๋˜์ง€ ์•Š์„ ๋•Œ ๊ธฐ์กด ์ƒํƒœ return
			return state;
} // Reducer๊ฐ€ ๋ฆฌํ„ดํ•˜๋Š” ๊ฐ’์ด ์ƒˆ๋กœ์šด ์ƒํƒœ!

โœ‹ย Reducer๊ฐ€ ์ˆœ์ˆ˜ํ•จ์ˆ˜์—ฌ์•ผ ์™ธ๋ถ€ ์š”์ธ์œผ๋กœ ์ธํ•ด ์—‰๋šฑํ•œ ๊ฐ’์œผ๋กœ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ์ผ์ด ์—†์–ด์ง

// ์—ฌ๋Ÿฌ ๊ฐœ์˜ Reducer๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ
// combineReduers() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ํ•˜๋‚˜์˜ Reducer๋กœ ํ•ฉ์ ธ์ค„ ์ˆ˜ ์žˆ์Œ
import {combineReduers} from 'redux';

const rootReducer = combineReducers({
	counterReducer,
	anyReduer,
	...
});

๐Ÿ“ย Store

์ƒํƒœ๊ฐ€ ๊ด€๋ฆฌ๋˜๋Š” ํ•˜๋‚˜๋ฟ์ธ ์ €์žฅ์†Œ ์—ญํ• 
์ฆ‰, state๊ฐ€ ์ €์žฅ๋˜์–ด ์žˆ๋Š” ๊ณต๊ฐ„

import {createStore} from 'redux';

const store = createStore(rootReducer);

๐Ÿ“Žย Redux Hooks

  1. useSelector()

    : ์ปดํฌ๋„ŒํŠธ & state๋ฅผ ์—ฐ๊ฒฐํ•˜์—ฌ Redux์˜ state์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋ฉ”์„œ๋“œ

    import {useSelector} from 'react-redux'; // 'redux'์—์„œ ๋ถˆ๋Ÿฌ์˜ค๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์— ์ฃผ์˜!
    
    const counter = useSelector(state => state.counterReducer)
    console.log(counter); // 1
  1. useDispatch()

    : Action ๊ฐ์ฒด๋ฅผ Reducer๋กœ ์ „๋‹ฌํ•ด์ฃผ๋Š” ๋ฉ”์„œ๋“œ

    import {useDispatch} from 'react-redux';
    
    const dispatch = useDispatch();
    dispatch(increase());
    console.log(counter) // 2
    
    dispatch(setNumber(10));
    console.log(counter) // 10

๐Ÿ“Žย Redux 3์›์น™

  1. Single source of truth
    : ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋Š” ํ•ญ์ƒ ๊ฐ™์€ ๊ณณ์—์„œ ๊ฐ€์ง€๊ณ  ์™€์•ผ ํ•จ
    ๐Ÿ‘‰ Redux์—์„  ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ํ•˜๋‚˜๋ฟ์ธ ๊ณต๊ฐ„์ธ Store ์กด์žฌ
  2. State is read-only
    : ์ƒํƒœ๋Š” ์ฝ๊ธฐ ์ „์šฉ
    ๐Ÿ‘‰ Action ๊ฐ์ฒด๊ฐ€ ์žˆ์–ด์•ผ๋งŒ ์ƒํƒœ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ
  3. Changes are mad with pure functions
    : ๋ณ€๊ฒฝ์€ ์ˆœ์ˆ˜ํ•จ์ˆ˜๋กœ๋งŒ ๊ฐ€๋Šฅ
    ๐Ÿ‘‰ย ์ƒํƒœ๊ฐ€ ์—‰๋šฑํ•œ ๊ฐ’์œผ๋กœ ๋ณ€๊ฒฝ๋˜๋Š” ์ผ์ด ์—†๋„๋ก ํ•ด์•ผ ํ•จ


โ˜‘๏ธย [Pair] Cmarket Redux

์˜ค๋Š˜์€ ์–ด์ œ ๊ตฌํ˜„ํ–ˆ๋˜ Cmarket Hooks ๊ธฐ๋Šฅ๋“ค์„ Redux๋ฅผ ํ™œ์šฉํ•˜์—ฌ Refactoringํ•ด๋ณด๋Š” ์‹œ๊ฐ„์„ ๊ฐ€์กŒ๋‹ค.

โŒ˜ Action

// action types
export const ADD_TO_CART = "ADD_TO_CART";
export const REMOVE_FROM_CART = "REMOVE_FROM_CART";
export const SET_QUANTITY = "SET_QUANTITY";
export const NOTIFY = "NOTIFY";
export const ENQUEUE_NOTIFICATION = "ENQUEUE_NOTIFICATION";
export const DEQUEUE_NOTIFICATION = "DEQUEUE_NOTIFICATION";

// actions creator functions

// ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋‹ด๊ธฐ ACTION
export const addToCart = (itemId) => {
  return {
    type: ADD_TO_CART,
    payload: { // payload๋กœ ๊ตฌ์ฒด์ ์ธ ๊ฐ’์„ ์„ค์ •ํ•ด์ฃผ๊ธฐ
      quantity: 1, // ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ๋‹ด์„ ๋• ์ˆ˜๋Ÿ‰์ด 1๋กœ ๊ณ ์ •๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ๊ฐ’์„ 1๋กœ ์„ค์ •
      itemId,
    },
  };
};

// ์žฅ๋ฐ”๊ตฌ๋‹ˆ ์‚ญ์ œ ACTION
export const removeFromCart = (itemId) => {
  return {
    type: REMOVE_FROM_CART,
    payload: {
      itemId, // ์‚ญ์ œ action์‹œ์—๋Š” quantity(์ˆ˜๋Ÿ‰) ๊ฐ’์€ ๊ตณ์ด ํ•„์š”์—†์œผ๋ฏ€๋กœ id๋งŒ ๊ฐ’ ์„ค์ •!
    },
  };
};

// ์ˆ˜๋Ÿ‰๋ณ€๊ฒฝ ACTION
export const setQuantity = (itemId, quantity) => {
  return {
    type: SET_QUANTITY,
    payload: {
      itemId,
      quantity,
    },
  };
};

// ...

โŒ˜ Dispath

  1. ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์ถ”๊ฐ€

    const handleClick = (item) => {
    	// ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ํด๋ฆญํ•œ ํ•ด๋‹น ํ’ˆ๋ชฉ์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด
        if (!cartItems.map((el) => el.itemId).includes(item.id)) {
          dispatch(notify(`์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ${item.name}์ด(๊ฐ€) ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`));
          dispatch(addToCart(item.id)); // dispatch()๋กœ ์•„์ดํ…œ ์ถ”๊ฐ€์— ๋Œ€ํ•œ ์•ก์…˜ ์ „๋‹ฌ
        } else { // ํด๋ฆญํ•œ ํ’ˆ๋ชฉ์ด ์ด๋ฏธ ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์žˆ๋Š” ํ’ˆ๋ชฉ์ด๋ผ๋ฉด
          dispatch(notify("์ด๋ฏธ ์ถ”๊ฐ€๋œ ์ƒํ’ˆ์ž…๋‹ˆ๋‹ค."));
        }
      };
  1. ์žฅ๋ฐ”๊ตฌ๋‹ˆ์—์„œ ํ’ˆ๋ชฉ ์‚ญ์ œ

    const handleQuantityChange = (quantity, itemId) => {
        dispatch(setQuantity(itemId, quantity)); // ์žฅ๋ฐ”๊ตฌ๋‹ˆ ์•„์ดํ…œ ์ˆ˜๋Ÿ‰๋ณ€๊ฒฝ ์•ก์…˜ ์ „๋‹ฌ
    };
  1. ์žฅ๋ฐ”๊ตฌ๋‹ˆ์—์„œ ํ’ˆ๋ชฉ ์ˆ˜๋Ÿ‰ ๋ณ€๊ฒฝ

    const handleDelete = (itemId) => {
        setCheckedItems(checkedItems.filter((el) => el !== itemId));
        dispatch(removeFromCart(itemId)); // ์žฅ๋ฐ”๊ตฌ๋‹ˆ ์•„์ดํ…œ ์‚ญ์ œ ์•ก์…˜ ์ „๋‹ฌ
    };

โŒ˜ Reducer

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

const itemReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_TO_CART:
      return Object.assign({}, state, { // Object.assign()์„ ํ†ตํ•ด ์ƒˆ๋กœ์šด ๊ฐ์ฒด ์ƒ์„ฑ
		// ๊ธฐ์กด ๋ฐ์ดํ„ฐ์— ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ ์ฆ‰, action.payload๋ฅผ ๋”ํ•ด์คŒ
        cartItems: [...state.cartItems, action.payload],
      });

    case REMOVE_FROM_CART:
	  // ์žฅ๋ฐ”๊ตฌ๋‹ˆ ์•„์ดํ…œ id์™€ action.payload์˜ id๋ฅผ ๋น„๊ตํ•ด์„œ ๊ฑฐ๋ฅธ ์•„์ดํ…œ
      let deletedItem = state.cartItems.filter(
        (el) => el.itemId !== action.payload.itemId
      );
      return Object.assign({}, state, {
        cartItems: deletedItem, // ๊ฑฐ๋ฅธ ์‚ญ์ œํ•  ์•„์ดํ…œ์„ cartItems๋กœ.
      });

    case SET_QUANTITY:
	  // ์žฅ๋ฐ”๊ตฌ๋‹ˆ ์•„์ดํ…œ id์™€ action.payload์˜ id๊ฐ€ ๊ฐ™์„ ๋•Œ์˜ ํ•ด๋‹น ์•„์ดํ…œ์˜ index
      let idx = state.cartItems.findIndex(
        (el) => el.itemId === action.payload.itemId
      );
      
      return Object.assign({}, state, {
        // ์ˆ˜์ •ํ•  ์•„์ดํ…œ ์ „, ํ›„๋Š” ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋กœ ์œ ์ง€ํ•˜๋˜,
        // ์ˆ˜์ •ํ•  ์•„์ดํ…œ์ธ action.payload๋ฅผ ๊ทธ ์ค‘๊ฐ„์— ๋ผ์›Œ์„œ ๋”ํ•ด์คŒ
        // ์ด ๋ธ”๋กœ๊ทธ์—์„œ ๋ฐฉ๋ฒ• ์•„์ด๋””์–ด ์–ป์Œ! -> https://florescene.tistory.com/m/199
        cartItems: [
          ...state.cartItems.slice(0, idx), // 
          action.payload,
          ...state.cartItems.slice(idx + 1),
        ],
      });
    default:
      return state;
  }
};

export default itemReducer;

๐Ÿซ  Reducer์˜ ๋ถˆ๋ณ€์„ฑ(Immutability)
: Redux์˜ state ์—…๋ฐ์ดํŠธ๋Š” immutableํ•œ ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝํ•ด์•ผ ํ•จ
๐Ÿคทโ€โ™€๏ธย why? Redux ์žฅ์  ์ค‘ ํ•˜๋‚˜์ธ ๋ณ€๊ฒฝ๋œ state๋ฅผ ๋กœ๊ทธ๋กœ ๋‚จ๊ธฐ๊ธฐ ์œ„ํ•ด
๐Ÿ‘‰ย Object.assign()์„ ํ†ตํ•ด ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ๋ฆฌํ„ด! (spread ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ์œ ์‚ฌํ•œ ๊ธฐ๋Šฅ)


โžฐ Test 12/12 Pass โœŒ๏ธ



์–ด์ œ๋„ ๊ทธ๋ ‡๊ณ , ์˜ค๋Š˜๋„ ์ €๋…์— ์™ธ์‹์„ ํ•ด์„œ ์ €๋… ์‹œ๊ฐ„์— ๊ณต๋ถ€๋Ÿ‰์ด ํ˜„์ €ํžˆ ์ค„์–ด๋“ค์—ˆ๋‹ค.๐Ÿฅฒ
์–ด์   ์•„์˜ˆ ๋ชปํ–ˆ๊ตฌ, ์˜ค๋Š˜๋„ ์ง‘์˜ค๋‹ˆ๊นŒ ์กธ๋ฆฌ๊ณ  ํ”ผ๊ณคํ•ด์„œ ์ง‘์ค‘์ด ์•ˆ๋˜๋Š” ๋Š๋‚Œ์“ฐ..
๋‚ด์ผ ํ•˜๋ฃจ๋„ ํ์ง€๋ถ€์ง€ ๋ณด๋‚ด์ง€ ๋ง๊ธฐ ๐Ÿ™
๊ทธ๋ฆฌ๊ณ  ๊ณ„์† ๊ณ ๋ฏผํ•˜๊ณ  ๊ณ ๋ฏผํ–ˆ๋˜ ๋ชจ๋˜์ž์Šค ๋”ฅ๋‹ค์ด๋ธŒ ๋„์„œ๋ฅผ ebook์œผ๋กœ ๊ตฌ๋งคํ–ˆ๋‹ค. 10์›”๊นŒ์ง€ ๋ถ€์ง€๋Ÿฐํžˆ ์ฝ์–ด๋ณด์ž ๐Ÿฆพ

profile
FE developer

0๊ฐœ์˜ ๋Œ“๊ธ€