FLUX 패턴과 Redux

jonyChoiGenius·2023년 1월 9일
0

React.js 치트 시트

목록 보기
11/22

flux 패턴

하나의 상태 저장소와 이를 변경하는 Dispatcher를 통해 단방향 순환 구조를 갖는 것.

  1. Action - Dispatcher - Model - View 의 구조를 갖는 패턴을 의미한다.
  2. Model(Store)라는 하나의 상태를 가지며, 계층 구조가 없이 흐름이 단방향이다.
  3. View(Controller View)는 Action을 호출할 수 있으며, Action - Dispatcher - Model - View - Action - Dispatcher...의 순환 구조가 된다.

리덕스

npm install --save redux
혹은 cdn을 통해 삽입할 수 있다.

  1. reducer(state, action) : action에 따라 state를 변경시키는 함수이다.
  2. Redux.createStore(리듀서함수) : 리듀서 함수를 주입받아 store를 반환한다. 이 때 store는 Object.assign() 등을 통해 새로운 객체를 반환하여 불변성을 유지하는 것이 좋다.
  3. action : dispatch에 주입되는 객체이며, type 프로퍼티를 반드시 가져야 한다.
  4. 스토어.getState() : store에 저장된 state의 데이터를 가져온다.
  5. 스토어.dispatch(액션객체): 액션 객체를 주입받아 리듀서 함수를 호출한다.
  6. 스토어.subscribe(콜백함수) : subscribe 메서드는 스토어의 변경을 감지하여 콜백 함수를 실행시킨다.

따라서 리덕스는 아래와 같이 작동하게 된다.

  1. Redux.createStore(reducer) 를 통해 store를 만든다. 최초에 reducer함수가 실행되며 state를 초기화하여 store에 저장한다.
//reducer 함수를 선언한다.
  function reducer(state,action){
      //state 초기화
      if(state === undefined){
          return{
              max_id:2,
              mode:'create',
              selected_id:1,
              contents:[
                  {id:1, title:"HTML", desc:'HTML is ...'},
                  {id:2, title:"CSS", desc:"CSS is ..."}
              ]
          }
      }

      var newState={};
      if(action.type === 'SELECT'){
          newState = Object.assign({},state,{selected_id:action.id, mode:'read'} );
      }
      else if(action.type === 'CREATE'){
        ..
      }
      else if(action.type === 'DELETE'){
        ...
      }
      else if(action.type === 'CHANGE_MODE') {
        ...
      }
      return newState;
  }

//Redux.createStore(reducer)를 통해 새로운 store를 만든다.
  var store = Redux.createStore(reducer);
  1. store.dispatch({type: ... [, state ]})는 store.state와 action을 인수로 reducer함수를 호출한다.
  2. reducer함수가 state를 변경하여 store에 반영한다.
function article() {
  //store에 저장된 state를 가져온다.
  var state = store.getState();

  if (state.mode === "create") {
    //html에서 submit 이벤트가 발생하면
    //store.dispatch에 action객체를 주입한다
    document.querySelector("#content").innerHTML = `
      <article>
        <form>
          <p>
              <input type="text" name="title" placeholder="title">
          </p>
          <p>
              <textarea name="desc" placeholder="description"></textarea>
          </p>
          <p>
              <input type="submit">
          </p>
      </form>
      </article>
          `;
  }
}

// dispatch가 리듀서 함수를 호출하여 store의 state를 변경하게 된다.
  1. store.subscribe(콜백)을 통해 store의 변경사항을 옵저빙한다.
// store.subscribe를 통해 store가 변경될 때마다 글의 내용과 목차의 DOM를 변경한다.
store.subscribe(article);
store.subscribe(TOC);
<!-- https://opentutorials.org/module/4078 -->
<!-- https://velog.io/@annie1004619/Redux-생활코딩 -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
  </head>
  <body>
    <div id="subject"></div>
    <div id="toc"></div>
    <div id="control"></div>
    <div id="content"></div>
    <script>
      // 제목
      function subject() {
        document.querySelector("#subject").innerHTML = `
            <header>
                <h1>WEB</h1>
                 Hello, WEB!
            </header>
            `;
      }

      // 목차
      function TOC() {
        var state = store.getState();
        var i = 0;
        var liTags = "";
        while (i < state.contents.length) {
          liTags += `
                <li>
                  <atoken interpolation">${state.contents[i].id}}
                    store.dispatch(action);
                  " href="${state.contents[i].id}">
                    ${state.contents[i].title}
                  </a>
                </li>`;
          i += 1;
        }
        document.querySelector("#toc").innerHTML = `
            <nav>
             <ol>
            ${liTags}
             </ol>
            </nav>
             `;
      }

      // 생성, 삭제 버튼
      function control() {
        document.querySelector("#control").innerHTML = `
            <ul>
            <li><a href="/create">create</a></li>
            <li><input type="button" value="delete"></li>
        </ul>
        `;
      }

      //컨텐츠의 내용
      function article() {
        var state = store.getState();

        if (state.mode === "create") {
          document.querySelector("#content").innerHTML = `
            <article>
             <form>
                <p>
                   <input type="text" name="title" placeholder="title">
                </p>
                <p>
                   <textarea name="desc" placeholder="description"></textarea>
                </p>
                <p>
                   <input type="submit">
                </p>
            </form>
            </article>
               `;
        } else if (state.mode === "read") {
          var i = 0;
          var aTitle, aDesc;
          while (i < state.contents.length) {
            if (state.contents[i].id === state.selected_id) {
              aTitle = state.contents[i].title;
              aDesc = state.contents[i].desc;
              break;
            }
            i = i + 1;
          }
          document.querySelector("#content").innerHTML = `
            <article>
             <h2>${aTitle}</h2>
             ${aDesc}
            </article>
               `;
        } else if (state.mode === "welcome") {
          document.querySelector("#content").innerHTML = `
            <article>
             <h2>welcome</h2>
             hello redux
            </article>
               `;
        }
      }

      // --------리덕스--------
      function reducer(state, action) {
        if (state === undefined) {
          return {
            max_id: 2,
            mode: "create",
            selected_id: 1,
            contents: [
              { id: 1, title: "HTML", desc: "HTML is ..." },
              { id: 2, title: "CSS", desc: "CSS is ..." },
            ],
          };
        }
        var newState = {};
        if (action.type === "SELECT") {
          newState = Object.assign({}, state, {
            selected_id: action.id,
            mode: "read",
          });
        } else if (action.type === "CREATE") {
          var newMaxId = state.max_id + 1;
          var newContents = state.contents.concat();
          newContents.push({
            id: newMaxId,
            title: action.title,
            desc: action.desc,
          });

          var newState = Object.assign({}, state, {
            max_id: newMaxId,
            contents: newContents,
            selected_id: newMaxId,
            mode: "read",
          });
        } else if (action.type === "DELETE") {
          var newContents = [];
          var i = 0;
          while (i < state.contents.length) {
            if (state.selected_id !== state.contents[i].id) {
              newContents.push(state.contents[i]);
            }
            i += 1;
          }
          newState = Object.assign({}, state, {
            contents: newContents,
            mode: "welcome",
          });
        } else if (action.type === "CHANGE_MODE") {
          newState = Object.assign({}, state, {
            mode: action.mode,
          });
        }
        console.log(action, state, newState);
        return newState;
      }
      var store = Redux.createStore(reducer);
      store.subscribe(article);
      store.subscribe(TOC);
      subject();
      TOC();
      control();
      article();
    </script>
  </body>
</html>

리액트 리덕스

컨테이너 컴포넌트에서 프레젠테이셔널 컴포넌트로 프롭스로 전달한다.
설치 yarn add redux react-redux

파일구조

  1. actions/constants/reducers
    action함수와 reducer함수를 관리하는 폴더, actionsType을 관리하는 폴더, 총 세 개의 폴더를 만드는 방식.

  2. Ducks 패턴
    modules 폴더 안에 action, reducer, actionsType을 하나의 파일로 관리.
    이하는 ducks의 예시이다.

모듈 생성

  1. 액션 타입 생성하기
    const 액션 = '모듈명/액션명'으로 타입을 생성한다. 모듈명을 함께 적음으로서 액션명의 중복을 방지할 수 있다.
// modules/counter.js
const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";
  1. 액션 함수 생성하기
    export 키워드를 통해 액션 함수의 생성과 동시에 export 하기
// modules/counter.js
const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";

export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
  1. reducer를 통해 상태 초기화 하기
// modules/counter.js
const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";

export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });

const initialState = {
  number: 0,
};

function counter(state = initialState, action) {
  switch (action.type) {
    case INCREASE:
      return {
        number: state.number + 1,
      };
    case DECREASE:
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
}

export default counter;
  1. 모듈을 불러오는 방법 (export default와 export의 차이)
import counter from "./counter";
import { increase, decrease } from "./counter";
// 한꺼번에 불러오고 싶을 때
import counter, { increase, decrease } from "./counter";

위의 내용을 참조하여 todo 모듈을 생성해보자.

props를 받아 해당 props가 액션타입에 들어감을 확인할 것.

  1. 액션 타입 및 액션 작성하기
// modules/todos.js

const CHANGE_INPUT = "todos/CHANGE_INPUT"; // 인풋 값을 변경함
const INSERT = "todos/INSERT"; // 새로운 todo를 등록함
const TOGGLE = "todos/TOGGLE"; // todo를 체크/체크 해제함
const REMOVE = "todos/REMOVE"; // todo를 제거함

export const changeInput = (input) => ({
  type: CHANGE_INPUT,
  input,
});

let id = 3; // insert가 호출될 때마다 1씩 더해집니다.
export const insert = (text) => ({
  type: INSERT,
  todo: {
    id: id++,
    text,
    done: false,
  },
});

export const toggle = (id) => ({
  type: TOGGLE,
  id,
});

export const remove = (id) => ({
  type: REMOVE,
  id,
});
  1. 리듀서 작성하기
// modules/todos.js
...

const initialState = {
  input:,
  todos: [
    {
      id: 1,
      text: '리덕스 기초 배우기',
      done: true
    },
    {
      id: 2,
      text: '리액트와 리덕스 사용하기',
      done: false
    }
  ]
};

function todos(state = initialState, action) {
  switch (action.type) {
    case CHANGE_INPUT:
      return {
        …state,
        input: action.input
      };
    case INSERT:
      return {
        …state,
        todos: state.todos.concat(action.todo)
      };
    case TOGGLE:
      return {
        …state,
        todos: state.todos.map(todo =>
          todo.id = = = action.id ? { …todo, done: !todo.done } : todo
        )
      };
    case REMOVE:
      return {
        …state,
        todos: state.todos.filter(todo => todo.id != = action.id)
      };
    default:
      return state;
  }
}

export default todos;

루트 리듀서 만들기

// modules/index.js
import { combineReducers } from "redux";
import counter from "./counter";
import todos from "./todos";

const rootReducer = combineReducers({
  counter,
  todos,
});

export default rootReducer;

import rootReducer from './modules';

리듀서 적용하기

  1. src/index.js에 store 생성하기
// src/index.js
import { createStore } from 'redux';
...

const store = createStore(rootReducer);
  1. provider로 프로젝트에 리덕스 적용하기
// index.js
import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import rootReducer from "./modules";

const store = createStore(rootReducer);

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

serviceWorker.unregister();

Redux DevTools

  1. 크롬 웹스토어에서 리덕스 데브툴 설치
  2. src/index.js에 리덕스 데브툴 적용
const store = createStore(
  rootReducer, /* preloadedState, */
  window._ _REDUX_DEVTOOLSEXTENSION  && window. _REDUX_DEVTOOLSEXTENSION _()
);
  1. 패키지를 사용하여 적용할 수도 있다. yarn add redux-devtools-extension
import { composeWithDevTools } from "redux-devtools-extension";
const store = createStore(rootReducer, composeWithDevTools());

containers 만들기

스토어에 접근하여 상태를 받아오고, 액션도 디스패치하는 컨테이너 컴포넌트를 만들자.

  1. 컨테이너 컴포넌트 생성
// containers/CounterContainers.js
import React from "react";
import Counter from "../components/Counter";

const CounterContainer = () => {
  return <Counter />;
};

export default CounterContainer;
  1. connect 함수 사용
    connect 함수는 connect(mapStateToProps, mapDispatchToProps)(연동할 컴포넌트)의 구조를 갖는다.
    mapStateToProps는 스토어의 상태를 props로 넘겨주는 함수,
    mapDispatchToProps는 디스패치를 props로 넘겨주는 함수.
import React from "react";
import { connect } from "react-redux";
import Counter from "../components/Counter";

const CounterContainer = ({ number, increase, decrease }) => {
  return (
    <Counter number={number} onIncrease={increase} onDecrease={decrease} />
  );
};

const mapStateToProps = (state) => ({
  number: state.counter.number,
});
const mapDispatchToProps = (dispatch) => ({
  // 임시 함수
  increase: () => {
    console.log("increase");
  },
  decrease: () => {
    console.log("decrease");
  },
});
export default connect(mapStateToProps, mapDispatchToProps)(CounterContainer);

CounterContainer로 선언한 컴포넌트에
mapStateToProps와 mapDispatchToProps를 넘겨주는 모습.
mapStateToProps에는 sotre의 counter리듀서로 생성된 state 중 number를 객체로 넘겨주고 있으며,
mapDispatchToProps는 increase라는 함수와 decrease라는 함수를 하나의 객체로 넘겨주고 있다.
해당 값들은 비구조화 할당으로 CounterContainer 컴포넌트의 props로 받아진다.

container 적용하기

// App.js
App.js;

import React from "react";
import Todos from "./components/Todos";
import CounterContainer from "./containers/CounterContainer";

const App = () => {
  return (
    <div>
      <CounterContainer />
      <hr />
      <Todos />
    </div>
  );
};

export default App;

Counter 컴포넌트를 CounterContainer로 대체하였다.
CounterContainer에서 mapStateToProps, mapDispatchToProps를 통해 state와 dispatch를 받은 후,
Counter 컴포넌트에 props로 넘겨주게 된다.

컨테이너의 state와 함수 부분을 아래와 같이 수정한다.

const mapStateToProps = (state) => ({
  number: state.counter.number,
});
const mapDispatchToProps = (dispatch) => ({
  increase: () => {
    dispatch(increase());
  },
  decrease: () => {
    dispatch(decrease());
  },
});
export default connect(mapStateToProps, mapDispatchToProps)(CounterContainer);

위와 같이 미리 mapStateToProps와 mapDispatchToProps를 선언하지 않고, connect 함수 내부에 직접 익명함수로 선언할 수도 있다.

export default connect(
  (state) => ({
    number: state.counter.number,
  }),
  (dispatch) => ({
    increase: () => dispatch(increase()),
    // 위 코드는 다음과 완전히 동일하게 작동함 increase: () => { return dispatch(increase()) }
    decrease: () => dispatch(decrease()),
  })
)(CounterContainer);

bindActionCreators 유틸 함수를 이용하여 여러개의 액션 함수를 dispatch할 수도 있다.

import { bindActionCreators } from 'redux';
...
export default connect(
  state => ({
    number: state.counter.number,
  }),
  dispatch =>
    bindActionCreators(
      {
        increase,
        decrease,
      },
      dispatch,
    ),
)(CounterContainer);

익명함수의 state, dispatch는 아래와 같이 비구조화 할당을 사용하여 가독성을 높일 수 있다.

export default connect(
  // 비구조화 할당을 통해 todos를 분리하여
  // state.todos.input 대신 todos.input을 사용
  ({ todos }) => ({
    input: todos.input,
    todos: todos.todos,
  }),
  {
    changeInput,
    insert,
    toggle,
    remove,
  }
)(TodosContainer);

redux-actions

디스패치와 리듀서를 더욱 쉽게 작성하게 해주는 라이브러리.
createAction으로 액션을 만들고, handleActions(업데이트 함수, 초기 상태)으로 액션을 관리한다.
yarn add redux-actions

// 기존 코드
// modules/counter.js
const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";

export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });

const initialState = {
  number: 0,
};

function counter(state = initialState, action) {
  switch (action.type) {
    case INCREASE:
      return {
        number: state.number + 1,
      };
    case DECREASE:
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
}

// 새로운 코드
import { createAction, handleActions } from "redux-actions";

const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";

export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);

const initialState = {
  number: 0,
};

const counter = handleActions(
  {
    [INCREASE]: (state, action) => ({ number: state.number + 1 }),
    [DECREASE]: (state, action) => ({ number: state.number - 1 }),
  },
  initialState
);

export default counter;

immer

yarn add immer
import produce from 'immer';

produce(변경할 값, 변경함수)

const state = {
  number: 1,
  dontChangeMe: 2,
};

//불변성을 지키지 않는 방식
state.number += 1;

//기존 방식
const nextState = { ...state, number: (state.number += 1) };

//immer
const nextState = produce(state, (draft) => {
  draft.number += 1;
});

react-redux Hooks

connect 대신 hooks를 사용할 수 있다.

  1. useSelector(상태를반환하는함수)
  2. useDispatch() : dispatch 함수를 반환함.
    const dispatch = useDispatch()
    dispatch({type :...})
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import Counter from "../components/Counter";
import { increase, decrease } from "../modules/counter";

const CounterContainer = () => {
  const number = useSelector((state) => state.counter.number);
  const dispatch = useDispatch();
  return (
    <Counter
      number={number}
      onIncrease={() => dispatch(increase())}
      onDecrease={() => dispatch(decrease())}
    />
  );
};

export default CounterContainer;

디스패치 함수에 useCallback을 이용하여 아래와 같이 최적화

import React, { useCallback } from 'react';
...
  const onIncrease = useCallback(() => dispatch(increase()), [dispatch]);
  const onDecrease = useCallback(() => dispatch(decrease()), [dispatch]);
  return (
    <Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease} />
  );
  1. useStore() : 리덕스 스토어 객체를 직접 사용할 때에 사용
const store = useStore();
store.dispatch({ type: "SAMPLE_ACTION " });
store.getState();
  1. useActions(액션배열, 의존자배열) 커스텀훅 출처
// @/lib/useActions.js
import { bindActionCreators } from "redux";
import { useDispatch } from "react-redux";
import { useMemo } from "react";

export default function useActions(actions, deps) {
  const dispatch = useDispatch();
  return useMemo(
    () => {
      if (Array.isArray(actions)) {
        return actions.map((a) => bindActionCreators(a, dispatch));
      }
      return bindActionCreators(actions, dispatch);
    },
    deps ? [dispatch, ...deps] : deps
  );
}
import useActions from "../lib/useActions";

// const dispatch = useDispatch();
// const onChangeInput = useCallback(input => dispatch(changeInput(input)), [
//   dispatch
// ]);
// const onInsert = useCallback(text => dispatch(insert(text)), [dispatch]);
// const onToggle = useCallback(id => dispatch(toggle(id)), [dispatch]);
// const onRemove = useCallback(id => dispatch(remove(id)), [dispatch]);

const [onChangeInput, onInsert, onToggle, onRemove] = useActions(
  [changeInput, insert, toggle, remove],
  []
);

액션 배열에 있는 액션들을 dispatch하며 새로운 배열을 만든다.
의존자 배열을 빈 배열로 초기화하여 최적화할 수 있다.

react-redux Hooks의 최적화

connect함수는 컨테이너 컴포넌트의 props가 바뀌지 않았다면 리렌더링이 방지되어 성능이 최적화된다.

반면 useSelector 훅은 부모컴포넌트가 리렌더링될 때에 최적화가 되지 않고 계속해서 리렌더링이 이루어 진다.

React.memo를 통해 리렌더링을 방지할 수 있다.

import React from 'react';
import { useSelector } from 'react-redux';
...
export default React.memo(TodosContainer);

미들웨어를 이용한 비동기작업 관리

yarn add redux react-redux redux-actions

미들웨어란 액션과 리듀서 사이의 중간자라 볼 수 있다.
액션에서의 처리 결과에 따라 미들웨어에서 작업한 후, 리듀서를 호출하거나, 액션을 취소하는 등의 작업을 진행한다.

미들웨어는 기본적으로 함수를 반환하는 함수이다.

store.dispatch({액션객체}는 middleware = store => next => action => {액션내용}으로 치환되는데,
이때 next()는 다음 미들웨어로 액션을 넘겨주거나, 다음 미들웨어가 없는 경우 store.dispatch(액션)을 실행시킨다.

// @/lib/loggerMiddleware.js
const loggerMiddleware = (store) => (next) => (action) => {
  console.group(action && action.type); // 액션 타입으로 log를 그룹화함
  console.log("이전 상태", store.getState());
  console.log("액션", action);
  next(action); // 다음 미들웨어 혹은 리듀서에게 전달
  console.log("다음 상태", store.getState()); // 업데이트된 상태
  console.groupEnd(); // 그룹 끝
};
export default loggerMiddleware;

// 아래와 같은 구조임
/*
const loggerMiddleware = function loggerMiddleware(store) {
  return function (next) {
    return function (action) {
      // 미들웨어 기본 구조
    };
  };
};
  */

위의 미들웨어는 액션의 타입과 상태를 하나의 그룹으로 묶어서 console에 출력한다.

만들어진 미들웨어는 index.js의 스토어에 아래와 같이 적용한다.

import loggerMiddleware from./lib/loggerMiddleware‘;

const store = createStore(rootReducer, applyMiddleware(loggerMiddleware));

redux-logger

yarn add redux-logger

// import loggerMiddleware from ‘./lib/loggerMiddleware‘;
import { createLogger } from ‘redux-logger‘;

const logger = createLogger();
const store = createStore(rootReducer, applyMiddleware(logger));

redux-logger는 액션의 타입과 디스패치된 시간, 스토어의 상태 등을 알록달록 이쁘게 출력해주는 미들웨어다.

redux-thunk

thunk는 특정 작업을 나중으로 미루기 위해 함수 형태로 감싼 단위를 의미한다. 즉 비동기 처리에서 넘겨주는 콜백함수가 thunk에 포함된다.
redux-thunk를 이용하면 함수를 디스패치할 수 있게된다.

yarn add redux-thunk

import { createLogger } from ‘redux-logger‘;
import ReduxThunk from ‘redux-thunk‘;

const logger = createLogger();
const store = createStore(rootReducer, applyMiddleware(logger, ReduxThunk));

위와 같이 store에 ReduxThunk를 등록하면 함수를 디스패치 할 수 있다.

//기본 방식
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });

function counter(state = initialState, action) {
  switch (action.type) {
    case INCREASE:
      return {
        number: state.number + 1,
      };
    case DECREASE:
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
}

//redux-action을 사용한 방식
const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";

export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);

const initialState = {
  number: 0,
};

const counter = handleActions(
  {
    [INCREASE]: (state, action) => ({ number: state.number + 1 }),
    [DECREASE]: (state, action) => ({ number: state.number - 1 }),
  },
  initialState
);

// redux-thunk를 사용한 방식
import { createAction, handleActions } from ‘redux-actions‘;


const INCREASE = ‘counter/INCREASE;
const DECREASE = ‘counter/DECREASE;

export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);

export const increaseAsync = () => dispatch => {
  setTimeout(() => {
    dispatch(increase());
  }, 1000);
};
export const decreaseAsync = () => dispatch => {
  setTimeout(() => {
    dispatch(decrease());
  }, 1000);
};

const initialState = 0; // 상태는 꼭 객체일 필요가 없습니다. 숫자도 작동해요.

const counter = handleActions(
  {
    [INCREASE]: state => state + 1,
    [DECREASE]: state => state - 1
  },
  initialState
);

export default counter;
// container/CounterContainer.js

//redux-action을 사용한 방식
import React from "react";
import { connect } from "react-redux";
import Counter from "../components/Counter";

const CounterContainer = ({ number, increase, decrease }) => {
  return (
    <Counter number={number} onIncrease={increase} onDecrease={decrease} />
  );
};

export default connect(
  (state) => ({
    number: state.counter.number,
  }),
  (dispatch) => ({
    increase: () => dispatch(increase()),
    // 위 코드는 다음과 완전히 동일하게 작동함 increase: () => { return dispatch(increase()) }
    decrease: () => dispatch(decrease()),
  })
)(CounterContainer);

//redux-thunk를 이용한 방식. state에는 숫자를, mapDispatchToProps에는 함수들을 넣어주었다.
const CounterContainer = ({ number, increaseAsync, decreaseAsync }) => {
  return (
    <Counter
      number={number}
      onIncrease={increaseAsync}
      onDecrease={decreaseAsync}
    />
  );
};

export default connect(
  state => ({
    number: state.counter
  }),
  {
    increaseAsync,
    decreaseAsync
  }
)(CounterContainer);

redux-saga

대부분의 비동기 처리는 redux-thunk에서도 가능하지만,
redux-saga는 다음의 상황에서 유리하다.

• 기존 요청을 취소 처리해야 할 때(불필요한 중복 요청 방지)

• 특정 액션이 발생했을 때 다른 액션을 발생시키거나, API 요청 등 리덕스와 관계없는 코드를 실행할 때

• 웹소켓을 사용할 때

• API 요청 실패 시 재요청해야 할 때

제너레이터 함수

ES6에 추가된 사양이다.
함수 내에 yield를 만나면 함수의 흐름을 중단하며,
함수를 호출할 때마다 다음 yield를 호출한다.

function* foo() {
  var index = 0;
  while (index <= 2)
    // when index reaches 3,
    // yield's done will be true
    // and its value will be undefined;
    yield index++;
}

var iterator = foo();
console.log(iterator.next()); // { value: 0, done: false }
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

next 함수에 value값을 넘겨서 yield로 멈춘 곳에 값을 추가해줄 수 있다.

function* sumGenerator() {
  console.log("sumGenerator가 만들어졌습니다.");
  let a = yield;
  let b = yield;
  yield a + b;
}

const sum = sumGenerator();
sum.next();
// sumGenerator가 만들어졌습니다.
// {value: undefined, done: false}
sum.next(1);
// {value: undefined, done: false}
sum.next(2);
// {value: 3, done: false}
sum.next();
// {value: undefined, done: true}

redux-saga는 const action = yield; 제너레이터.next({ type: 'TEST' });와 같이 next 함수에 type 값을 포함한 객체를 넘겨주며 작동한다.

(리덕스 사가 작성중)

profile
천재가 되어버린 박제를 아시오?

0개의 댓글