전역 상태 관리 Redux

이선호·2021년 12월 6일
0
post-thumbnail

Redux는 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너입니다. (Redux Docs)

📍 단방향 데이터 바인딩을 하는 Flux 패턴 의 자바스크립트 구현체라고 생각하자

redux는 기본적으로 하나의 Store 멀티 Reducer의 형태를 갖는다.

즉, store는 하나 그 하나에 여려개의 Reducer가 있다.


Redux를 사용하는 이유는?

리덕스를 사용하면 컴포넌트의 상태 업데이트 관련 로직을 다른 파일로 분리시켜서 효율적으로 관리할 수 있다.


리덕스의 흐름

Redux란,

1) View에서 유저가 일으키는 행동에 맞게 Action 객체가 생성되고,

2) Action은 Dispatcher를 통해 Reducer로 전달되고,

3) Action의 type에 따라 Reducer 내에 미리 정해져 있던 로직이 Store를 변경하고,

4) 변경된 Store의 내용이 View로 반영되는 패턴이라고 요약할 수 있음.


(1) View

  • 유저가 사용하는 화면. 버튼, React 컴포넌트 등의 UI 구성 요소.

(2) Action / Action Creator

상태에 변화가 필요하다면 액션을 일으켜야한다.

  • Action은 중앙 저장소에 저장된 state에 “무슨” 동작을 할 것이지 적어놓는 객체입니다.
    Action에는 type이 필수로 필요합니다.

     //action types
     const ADD_CART = 'ADD_CART'
       ```
  • Action Creator는 이러한 Action 객체를 정해진 틀에 맞게 리턴하는 단순한 함수.
    📍 Action을 reducer로 보내는 역할을 하는 건 아님.
    ```jsx
    export const addCart = (item) => { // 액션 "생성 함수"
      return {
        type: "ADD_CART", // 액션 "객체"
        payload: item,
      };
    };
    ```
  • Action은 Store에 담긴 데이터를 변형시킬 수 있는 유일한 방법
    • store.dispatch 함수의 인자로 담겨 reducer어떻게 store를 변경시킬 것인지에 대한 정보를 제공 하는 역할
  • 필수적인 type 프로퍼티를 제외하면, 사용자의 필요에 따라 추가 가능
    const ADD_TODO = {
    	type: "ADD_CART",
    	payload: { content: "출근하기", priority: 1 }
    }

(3) Dispatcher

  • Action 객체를 Reducer에 보내는 역할을 하는 함수. store.dispatch() 형태로 제공됨.
  • 기본 dispatch 함수는 반드시 동기적으로 처리되어야 함.
  • 만약 비동기 Action이 필요하다면 비동기 처리가 완료된 이후에 Action을 Dispatch 하거나, 미들웨어(Action이 Reducer로 들어가기 전에 거쳐가는 곳)를 활용해 비동기 처리를 해줘야 함.
  //addTodo를 담고 reducer로 향하는 dispatch열차 
  dispatch(addTodo(text))

위와 같이 작성하게 되면 매번 dispatch안에 action creator를 호출해서 사용해야하기 때문에 아래와 같이 함수를 만들어서도 사용함.

const boundAddTodo = text => dispatch(addTodo(text))
boundAddTodo(text)

(4) Reducer

dispatch 로 넘어온 actiontype을 확인해서 그에 맞는 동작을 하는 곳.
동작을 하기 때문에 function으로 작성이 됩니다.

Reducer는 (previousState, action) => newState의 형태를 가지고, 순수함수이며 예측 가능해야 합니다.

Reducer는 업데이트 되기 이전의 Store를 기반으로 새로 받은 Action에 미리 준비된 로직을 처리해 새로운 Store를 리턴하는 함수입니다. 즉, Reducer는 감지된 Action 타입에 따라 이벤트를 처리하는 이벤트 리스너로도 생각할 수 있습니다.

  // Array.prototye.reduce()
  const cart = [
  	{ id: 1, name: "청바지", price: 10000, quantity: 2 },
  	{ id: 2, name: "반바지", price: 10000, quantity: 1 },
  	{ id: 3, name: "반팔", price: 10000, quantity: 2 },
  ]
  
  const totalPrice = cart.reduce((acc, cur) => {
    return acc + cur.price * cur.quantity
  }, 0)
  ```
  
  redux의 Reducer 역시 Store와 Action을 받아 새로운 Store로 값을 합쳐 내보냅니다.
  
  
``` jsx
  const INITIAL_STATE = [];
  
  // redux/cart.js
  export default function cartReducer(state = INITIAL_STATE, action) {
    switch (action.type) {
      case "ADD_ITEM":
        return [...state, action.payload]; // 이전 상태에 새로운 item을 추가
  		case "DELETE_ITEM":
  			return state.filter((product) => product.id !== payload.id);
      default:
        return state; // 해당 사항 없으면 이전 상태를 그대로 리턴
    }
  }
  
  // redux/index.js
  import { combineReducers } from "redux";
  import cartReducer from "./cartReducer";
  
  export default combineReducers({ cartReducer });
  • 리듀서는 다음과 같은 요소로 구분됩니다. 코드 레벨에서의 구현은 뒤에서 자세히 살펴보겠습니다.
    • Root Reducer: 실제로 createStore의 첫 번째 인자로 전달되는 함수입니다. 유일하게 (state, action) => newState 형태의 로직을 가져야 합니다.
    • Slice Reducer: 상태 트리의 일부분만을 업데이트하는 리듀서입니다. 여러 Slice Reducer들이 결합(combineReducers)되어 Root Reducer를 구성하게 됩니다.

:: 주의 사항

  • 불변성을 지켜 업데이트 해야 합니다.
    • 기존 값을 직접 수정하면 안됩니다. React에서 setState로 그렇게 해왔듯 기존 값을 복사하고 새롭게 복사된 값을 덮어쓰는 방식으로 업데이트 해야 합니다.
  • Reducer 내부에서 비동기나 여타 순수하지 않은 로직(Side Effect)을 처리하면 안됩니다.
    • ex. Promise(fetch), Math.random(), new Date()

(5) Store

스토어에는 상태가 들어있다. 하나의 프로젝트는 하나의 스토어만 가질 수 있다.

store는 모든 컴포넌트에서 사용할 수 있는 Global State를 저장해놓는 저장소 입니다.

하지만 이 state는 엄격하게 관리해야 하기 때문에 dispatch라는 함수를 통해서만 state에 접근이 가능하다.

import { createStore } from 'redux'
import todoApp from './reducers'

const store = createStore(todoApp)
  • 항상 직렬화(Serialization) 가능해야 하기 때문에 JSON으로 쉽게 변환할 수 없는 것들은 제외하는게 좋습니다(ex. 함수, Promise 등)

(6) Middleware

  • 비동기 API 호출 등 순수하지 않은 요청(Side Effect)을 처리하거나, Redux Store로 전달되는 Action 등을 로깅하는 장소이며 Reducer가 액션을 처리하기 전에 실행되는 함수입니다.
  • Middleware를 설정하지 않으면 dispatch한 액션은 곧바로 리듀서로 보내집니다.
  • 대표적인 Middleware로 redux-logger, redux-thunk, redux-saga 등이 있습니다.

0개의 댓글