react6

Redux Advanced

Async Action with Redux

  • 비동기 작업을 어디서 하느냐가 제일 중요
    • 액션을 분리합니다.
    • Start
    • Success
    • Fail
    • …등
  • dispatch 를 할때 해줍니다.
    • 당연히 리듀서는 동기적인 것 => Pure
    • dispatch 도 동기적인 것

비동기 처리를 위한 액션 추가 (예시)

// 액션 정의
export const START_RECEIVE_BOOKS = "START_RECEIVE_BOOKS";
export const END_RECEIVE_BOOKS = "END_RECEIVE_BOOKS";
export const ERROR_RECEIVE_BOOKS = "ERROR_RECEIVE_BOOKS";

// 액션 생성자 함수
export function startReceiveBooks() {
  return {
    type: START_RECEIVE_BOOKS
  };
}

export function endReceiveBooks(books) {
  return {
    type: END_RECEIVE_BOOKS,
    books
  };
}

export function errorReceiveBooks() {
  return {
    type: ERROR_RECEIVE_BOOKS
  };
}

mapDispatchToProps => dispatch

const mapDispatchToProps = dispatch => ({
  requestBooks: async token => {
    dispatch(startLoading());
    dispatch(clearError());
    try {
      const res = await axios.get("https://api.marktube.tv/v1/book", {
        headers: {
          Authorization: `Bearer ${token}`
        }
      });
      dispatch(setBooks(res.data));
      dispatch(endLoading());
    } catch (error) {
      console.log(error);
      dispatch(setError(error));
      dispatch(endLoading());
    }
  }
});

Books.jsx

import React, { useEffect } from "react";

const Book = props => <div>title : {props.title}</div>;

const Books = ({ token, loading, error, books, requestBooks }) => {
  useEffect(() => {
    requestBooks(token); // 컨테이너로 로직을 옮겼음.
  }, [token, requestBooks]);
  return (
    <div>
      {loading && <p>loading...</p>}
      {error !== null && <p>{error.message}</p>}
      {books.map(book => (
        <Book title={book.title} key={book.bookId} />
      ))}
    </div>
  );
};

export default Books;

리덕스 미들웨어

  • 미들웨어가 “디스패치” 의 앞뒤에 코드를 추가할수 있게 해줍니다.
  • 미들웨어가 여러개면 미들웨어가 “순차적으로” 실행됩니다.
  • 두 단계가 있습니다.
    • 스토어를 만들때, 미들웨어를 설정하는 부분
      • {createStore, applyMiddleware} from redux
    • 디스패치가 호출될때 실제로 미들웨어를 통과하는 부분
  • ​dispatch 메소드를 통해 store로 가고 있는 액션을 가로채는 코드
function middleware1(store) {
  return next => {
    console.log("middleware1", 1);
    return action => {
      console.log("middleware1", 2);
      const returnValue = next(action);
      console.log("middleware1", 3);
      return returnValue;
    };
  };
}

function middleware2(store) {
  return next => {
    console.log("middleware2", 1);
    return action => {
      console.log("middleware2", 2);
      const returnValue = next(action);
      console.log("middleware2", 3);
      return returnValue;
    };
  };
}

applyMiddleware(함수1, 함수2, …)

import { createStore, applyMiddleware } from 'redux';

function middleware1(store) {...}

function middleware2(store) {...}

const store = createStore(reducer, applyMiddleware(middleware1, middleware2));

middleware 에서 store 접근

function middleware1(store) {
  return next => {
    console.log("middleware1", 1, store.getState());
    return action => {
      console.log("middleware1", 2, store.getState());
      const returnValue = next(action);
      console.log("middleware1", 3, store.getState());
      return returnValue;
    };
  };
}

react-devtools

npm install -D redux-devtools-extension

composeWithDevTools

import { createStore, applyMiddleware } from "redux";
import reducers from "./reducers";
import { composeWithDevTools } from "redux-devtools-extension";

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

export default store;

redux-thunk

  • 리덕스 미들웨어
  • 리덕스를 만든 사람이 만들었음. (Dan)
  • 리덕스에서 비동기 처리를 위한 라이브러리
  • 액션 생성자를 활용하여 비동기 처리
  • 액션 생성자가 액션을 리턴하지 않고, 함수를 리턴함.
npm i redux-thunk

import thunk from ‘redux-thunk’;

import { createStore, applyMiddleware } from "redux";
import reducers from "./reducers";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk"; // import

const store = createStore(
  reducers,
  composeWithDevTools(applyMiddleware(thunk)) // 미들웨어 설정
);

export default store;

Before Using thunk

const mapDispatchToProps = dispatch => ({
  requestBooks: async token => {
    dispatch(startLoading());
    dispatch(clearError());
    try {
      const res = await axios.get("https://api.marktube.tv/v1/book", {
        headers: {
          Authorization: `Bearer ${token}`
        }
      });
      dispatch(setBooks(res.data));
      dispatch(endLoading());
    } catch (error) {
      console.log(error);
      dispatch(setError(error));
      dispatch(endLoading());
    }
  }
});

Use thunk

// BooksContainer.jsx
const mapDispatchToProps = dispatch => ({
  requestBooks: async token => {...},
  requestBooksThunk: token => {
    dispatch(setBooksThunk(token));
  }
});

// actions/index.js
export const setBooksThunk = token => async dispatch => {
  dispatch(startLoading());
  dispatch(clearError());
  try {
    const res = await axios.get("https://api.marktube.tv/v1/book", {
      headers: {
        Authorization: `Bearer ${token}`
      }
    });
    dispatch(setBooks(res.data));
    dispatch(endLoading());
  } catch (error) {
    console.log(error);
    dispatch(setError(error));
    dispatch(endLoading());
  }
};

redux-promise-middleware

npm i redux-promise-middleware

import promise from ‘redux-promise-middleware’;

import { createStore, applyMiddleware } from "redux";
import reducers from "./reducers";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import promise from "redux-promise-middleware"; // import

const store = createStore(
  reducers,
  composeWithDevTools(applyMiddleware(thunk, promise)) // 미들웨어 설정
);

export default store;

payload 가 Promise

// actions/index.js
export const setBooksPromise = token => ({
  type: BOOKS,
  payload: axios.get("https://api.marktube.tv/v1/book", {
    headers: {
      Authorization: `Bearer ${token}`
    }
  })
});

액션의 type 에 접미사를 붙인 액션을 자동 생성하고 자동으로 dispatch 시킴

// actions/index.js
export const BOOKS = "BOOKS";
export const BOOKS_PENDING = "BOOKS_PENDING";
export const BOOKS_FULFILLED = "BOOKS_FULFILLED";
export const BOOKS_REJECTED = "BOOKS_REJECTED";

// reducers/loading.js
export default function loading(state = initialState, action) {
  switch (action.type) {
    case BOOKS_PENDING:
      return true;

    case BOOKS_FULFILLED:
      return false;

    case BOOKS_REJECTED:
      return false;

    default:
      return state;
  }
}

payload 로 들어오는 데이터를 활용하여 표현

// reducers/books.js

const books = (state = initialState, action) => {
  switch (action.type) {
    case BOOKS_FULFILLED: {
      return [...action.payload.data]
    }

  ...
}

카테고리:

업데이트: