[React] study 6주차 (2) - Redux (Flux pattern, redux-thunk, redux-toolkit)

newsilver·2022년 2월 28일
1

react-study

목록 보기
7/9
post-thumbnail

6주차 과제는 Redux!
redux의 비동기 처리 방법, redux toolkit을 알아보았다.

$ npm add redux react-redux redux-devtools-extension redux-logger

Flux 패턴이란?

MVC 패턴의 단점을 보완하기 위해 페이스북에서 만든 단방향 데이터 흐름을 위한 패턴이다.

MVC 패턴의 문제점

MVC 패턴은 Model과 View가 양방향 패턴에서 나오는 의존성을 가진다는 특성이 있다.
app의 규모가 커질수록 시스템의 복잡도가 높아지고, 예측 불가능한 코드를 만들게 되며, 예측 못할 버그들이 쏟아질 가능성이 높아진다.

Flux 패턴

dispatcher, store, view 모두 독립적인 노드로 입력과 출력이 완전히 구분된다.

  • Action / Action Creator
    새로운 데이터를 포함하고 있는 (데이터를 직접 가지고 있지 않고 action만 취할 수도 있다.) 간단한 객체로 type 프로퍼티로 구분된다.
    action creator는 새로 발생한 action type과 payload를 action message로 묶어 dispatcher로 전달한다.

  • Dispatcher
    action을 받아서 store로 넘겨준다. dispatcher에는 callback을 등록할 수 있는데, 이 callback에서 data의 여러 처리가 일어난다.

  • Store
    store는 dispatcher에 callback에서 처리된 data를 저장하는 곳이다. store 내부의 data가 변경되면 이 변경을 감지해서 view에 해당 data를 알려준다.

  • View
    React에 해당되는 부분이다. controller view는 store에서 변경된 데이터를 가져와 모든 자식 view에게 데이터를 분배한다. 데이터를 넘겨받은 view는 화면을 새로 렌더링한다.

결론적으로 데이터 흐름은 한 방향으로 강제되고, 모든 상태는 store에 모여 있으므로 변경 사항을 여러 컴포넌트로 전달하기 쉽다.

Redux

redux는 flux 패턴을 구현한 라이브러리이다.
data 처리를 reducer(= dispatch에 등록된 callback) 가 한다.

redux-thunk

Redux에서 비동기를 간단하게 처리할 수 있는 미들웨어이다.
객체 대신 함수를 생성하는 액션 생성함수를 작성할 수 있게 해준다.

$ npm install --save redux-thunk

src/index.js

import ReduxThunk from 'redux-thunk';
import { createStore, applyMiddleware } from "redux";

const store = createStore(reducers, applyMiddleware(thunk));

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.querySelector("#root")
);

redux toolkit

많은 보일러 플레이트를 요구하고 복잡한 코드를 작성해야 하는 redux의 단점을 보완하기 위해 나온 것이 redux toolkit (RTK)이다.
redux-thunk를 기반으로 사용하지만, redux-saga 등 다른 미들웨어도 사용 가능하다.

$ npm install @reduxjs/toolkit

redux toolkit API

configureStore

import { configureStore } from '@reduxjs/toolkit';

const store = configureStore({ reducer: rootReducer });

기본 미들웨어로 redux-thunk를 추가하고 개발 환경에서 리덕스 개발자 도구(Redux DevTools Extension)를 활성화해준다.
reducer 필드를 필수적으로 넣어주어야 하며 default 로 redux devtool 을 제공한다.

createAction

createAction은 action을 보다 간결하게 만들어 줄 수 있게 해준다.

import { createAction } from '@reduxjs/toolkit';

export const addComment = createAction(ADD, saveData => saveData);
export const deleteComment = createAction(DELETE, target => target);

이 생성함수를 호출할 때 parameter를 추가로 넣어준다면 payload 필드에 자동으로 들어가게 된다.

const comment1 = addComment(["user1","comment contents"]);
// return ({
//	type: ADD,
//  payload: ["user1","comment contents"]
// });

createReducer

createReducer를 사용하면 action type을 구분하는 switch문과 default문을 생략할 수 있다.

import { createReducer } from '@reduxjs/toolkit';

export default createReducer(initicalState, {
  Add: (state, { payload: saveData }) => {
    const [userName, content] = saveData;
     const newCommentObject = {
        userName: userName,
        content: content,
        date: currentTime(),
        id: `${userName+currentTime()}`
      }
      const commentsArray = [...state, newCommentObject];
    return commentsArray;
  },
  DELETE: (state, { payload: target}) => {
    const deletedArray = state.filter(comment => comment.id !== target);
    return deletedArray;
  }
})

첫 번째 파라미터인 initialState가 default 값이기 때문에 default문을 생략할 수 있다.

createSlice

createSlice는 action 과 reducer 전부를 가지고 있는 함수이다.
reducer는 action creator와 action type을 선언해 사용했지만,
createSlice 의 reducers는 action을 선언하고 해당 action 이 dispatch 되면 바로 state를 가지고 action을 처리한다.
action type, action creator, reducer 기능이 모두 합쳐져 있다.

import { createSlice } from '@reduxjs/toolkit';

export const commentReducer = createSlice({
  name: 'commentReducer',
  initialState: [],
  reducers: {
    Add: (state, { payload: saveData }) => {
      const [userName, content] = saveData;
      const newCommentObject = {
        userName: userName,
        content: content,
        date: currentTime(),
        id: `${userName+currentTime()}`
      }
      const commentsArray = [...state, newCommentObject];
      return commentsArray;
    },
    DELETE: (state, { payload: target}) => {
      const deletedArray = state.filter(comment => comment.id !== target);
      return deletedArray;
    }
  }
});

name 속성은 액션의 경로를 잡아줄 해당 이름을 나타내고, initialState 는 초기 state를 나타낸다.
reducers 속성에는 createReducer에서 작성하였던 함수 내용 그대로 가져온다.

불변성 관리

createReducercreateSlice 함수는 immer 라이브러리를 내재하고 있기 때문에 불변성을 자동으로 관리해준다.
따라서 새로운 state 객체를 만들어 return 할 필요가 없어지고 state를 직접 변경이 가능하다.

export const commentReducer = createSlice({
  name: 'commentReducer',
  initialState: [],
  reducers: {
    Add: (state, { payload: saveData }) => {
      const [userName, content] = saveData;
      const newCommentObject = {
        userName: userName,
        content: content,
        date: currentTime(),
        id: `${userName+currentTime()}`
      }
      state.push(newCommentObject);	// state에 push 사용
      return state;
    },
    DELETE: (state, { payload: target}) => {
      const deletedArray = state.filter(comment => comment.id !== target);
      return deletedArray;
    }
  }
});

스스로 불변성 관리를 해 새로운 state 객체의 형태를 이루어주게 한다.

scope

reducer는 기본적으로 함수 자체를 하나의 scope로 취급한다.

export default function testReducer(state=initialState, action) {
  switch(action.type) {
    case A:
      let fruit='apple';
      return
    case B:
      let fruit='orange'; 
      // Parsing error: Identifier 'fruit' has already been declared.
      return;
    case C:
      let fruit='banana';
      // Parsing error: Identifier 'fruit' has already been declared.
      return;
    default:
      return state;
  }
}

하지만 createReducercreateSlice 함수는 각 action type마다 코드블럭을 scope로 잡기 때문에 변수를 scope 단위로 사용할 수 있다.

const testReducer = createSlice({
  name: 'testReducer',
  initialState: [],
  reducers: {
    A: (state) => {
      let fruit='apple';
    },
    B: (state) => {
      let fruit='orange'; 
    },
    C: (state) => {
      let fruit='banana';
    }
  }
})
profile
이게 왜 🐷

0개의 댓글