React + Redux

2 minute read

컴포넌트 구성이 깊고 복잡해지면서, 소위 드릴링이라고 하는 부모 컴포넌트의 상태 전달이 굉장히 번거로워진다. React에서도 Context API로 드릴링 없이 부모의 상태를 가져올 수 있긴 하다. Redux도 Context API를 가지고 만든 라이브러리인지라, 전역상태 관리 측면에서는 거의 차이점이 없다고 한다. Redux는 전역상태관리 외에도 다양한 기능을 제공하며, 확장성이 뛰어나다.

// index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

import store from "./store/store";
import { Provider } from "react-redux";

ReactDOM.render(
    <Provider store={store}>
      <App />
    </Provider>,
    document.getElementById("root")
)
// store/store.js
import { compose, createStore, applyMiddleware } from "redux";
import rootReducer from "../reducers/index";
import thunk from "redux-thunk";

// Redux Dev Tools 기본 Setting
// https://extension.remotedev.io/#usage의 Advanced store setup을 참조할 것.
// createStore에서 compose 대신 composeEnhancer를 사용한다.
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
  : compose;

// 1. createStore(reducer function, preloaded state(=initial state), enhancer(3rd party store enhancer))
// 아래 예제는 rootReducer 객체를 reducer 함수로 전달. thunk(비동기 호출을 가능하게 하는 미들웨어)를 Enhahcer로 사용한다.
// 반환값인 Store는 states를 담고 있는 객체.

// 2. applyMiddleWare(...)는 dispatch 메서드의 wrapper를 무엇으로 사용할 것인지 정의한다. 미들웨어 확장 함수.
// 각 미들웨어의 골조는 다음과 같다. ({getState, dispatch) => (next) => action})
// 즉, Dispatch와 관련이 있는 미들웨어를 병합할 때 사용한다.

// 3. compose(...functions) 또는 composeEnhancer
// 함수 실행순서는 가장 마지막 인자 ~ 첫 번째 인자 순이다. 반환값이 다음 함수의 매개인자로 전달되는 방식이다. 체이닝.
// Dispatch와 관련이 없는 추가 인핸서를 병합해야할 때 사용한다.
const store = createStore(rootReducer, composeEnhancer(applyMiddleware(thunk)));

export default store;
// reducers/index.js
// 1. combineReducer는 Redux Store 내부의 여러 상태들의 리듀서를 관리하기 위해 사용. 반환값은 Reducer 함수이다.
const rootReducer = combineReducers({
  // useSelect를 사용하여 상태를 가져올 때 rootReducer의 "key"를 이용한다.
  items: itemReducer,
  notifictations: notificationReducer
})

// actions/index.js
// 1. 동기함수
export const setQuantity = (itemId, quantity) => {
  return {
    type: "SET_QUANTITY",
    payload: {
      quantity,
      itemId
    }
  }
}
// 2. 비동기함수, Redux thunk를 사용한다.
export const notify = (message, dismissTime = 3000) => dispatch => {
  const uuid = Math.random();
  dispatch(enqueueNotification(message, dismissTime, uuid))
  // 비동기 호출.
  setTimeout(() => dispatch(dequeueNotification), dismissTime);
}


// reducers/itemReducer.js
const itemReducer = (state = initialState, action) => {
  switch (action.type) {
    // P.S. Switch-Case문에서 동일명 변수를 여러 케이스에서 사용할 경우 {}로 스코프를 구분해야 한다. 이중선언 오류 발생함.
    case ADD_TO_CART: {...}
    case REMOVE_FROM_CART: {...}
    case SET_QUANTITY: {
      const idx = state.cartItems.findIndex(el => el.itemId === action.payload.itemId);
      return Object.assign({}, state, {
        cartItems: [
          ...state.cartItems.slice(0, idx),
          { itemId: action.payload.itemId, quantity: action.payload.quantity },
          ...state.cartItems.slice(idx + 1)
        ]
      })
    }
    default: 
      return state;
  }
}
// 1. React: useState
const [count, setCount] = useState(initialValue);

// 2. React: useEffect
userEffect(() => {
  // ComponentDidMount, ComponentDidUpdate
  ...
  return () => {
    //ComponentWillUnmount(Clean-up)
    ...
  }
})

// 3. Redux: useSelector, Redux Store에서 특정 State를 불러온다. rootReducer에 전달한 Key 이름으로 불러올 수 있다.
const state = useSelector(state => state.items)
const { items, cartItems } = state;

// 4. Redux: useDispatch, Reducer에 action 객체를 전달하는 역할. 액션생성자의 결과값을 전달해야 한다. dispatch(actionCreator(payload parameter))으로 실행한다. 

const handleQuantityChange = (quantity, itemId) => {
  dispatch(setQuantity(itemId, quantity));
}

Categories: ,

Updated:

Leave a comment