Redux로 상태관리하기 - Redux Advanced (1) - 2

lbr·2022년 8월 16일
0

리덕스 미들웨어

redux Middleware는 리덕스 사이트의 advanced 항목에서 확인해 볼 수 있습니다.

  • 미들웨어가 "디스패치" 의 앞뒤에 코드를 추가할수 있게 해줍니다.
  • 미들웨어가 여러개면 미들웨어가 "순차적으로" 실행됩니다.
  • 두 단계가 있습니다.
    - 스토어를 만들때, 미들웨어를 설정하는 부분 {createStore, applyMiddleware} from redux
    - 디스패치가 호출될때 실제로 미들웨어를 통과하는 부분
  • dispatch 메소드를 통해 store로 가고 있는 액션을 가로채는 코드

실습

미들웨어 사용을 위한 코드 작성

미들웨어는 store를 설정하는 부분에서 설정해주어야 합니다.

const store = createStore(reducer, {이 부분});

createStore의 두번째 인자로 인헨서라는 것을 추가해 줄 수 있는데 이 것이 미들웨어입니다.

미들웨어는 function입니다. 인자로 store를 받을 수 있습니다.(아래)

import { applyMiddleware, createStore } from "redux";

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

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

리턴은 함수입니다. next라고 인자로 들어오는데, 이 next는 현재의 미들웨어가 아니라 다음의 미들웨어를 지칭하는 합니다. 다음 미들웨어가 없으면 store로 이동하여 dispatch합니다.

아래는 실행결과

새로 고침으로 첫 mount 때에만 console로 0, 1, 2, 3 이 출력되고, 이후에 List에 item을 추가할 때에는 2, 3만 출력됩니다.

미들웨어의 실행 순서

미들웨어를 하나 더 추가하고(middleware2) middleware1 다음순서로 등록하겠습니다. (아래)

import { applyMiddleware, createStore } from "redux";

function middleware1(store) {
  console.log("middleware1", 0);
  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) {
  console.log("middleware2", 0);
  return (next) => {
    console.log("middleware2", 1);
    return (action) => {
      console.log("middleware2", 2);
      const returnValue = next(action);
      console.log("middleware2", 3);

      return returnValue;
    };
  };
}

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

아래는 실행결과

우리가 이 함수안에서 할 수 있는 일들

store를 최초의 인자로 받았기 때문에 store에 getState()를 할 수 있고, store에 dispatch()를 할 수 있습니다.

그래서 state를 얻어서 다른 처리를 하거나, 아니면 dispatch를 추가로 더 보내는 등의 작업을 할 수 있습니다.

보통 실무에서 직접 middleware를 만들어서 사용하는 경우는 없고, redux의 부가기능을 추가하기 위해서 여러가지 redux 라이브러리 들이 준비되어 있습니다. 우리는 이런 라이브러리들을 활용하여 redux store를 좀 더 고급기능으로 사용할 수 있게 됩니다.

다음시간에는 store에 미들웨어를 설치해서 어떤 도움을 받을 수 있는지 알아보겠습니다.

redux-devtools

이번에는 redux에 redux-devtools를 적용해 보겠습니다.

redux-devtools도 미들웨어를 설치해서 우리들의 브라우저에 있는 devtools에 연결하는 작업이 필요합니다.

설치

npm i redux-devtools-extension -D

코드설정

  1. import { composeWithDevTools } from "redux-devtools-extension" import해줍니다.
  2. applyMiddleware()를 실행한 결과를 composeWithDevTools의 인자로 넣습니다.
import { applyMiddleware, createStore } from "redux";
import reducer from "./reducers/reducer";
import { composeWithDevTools } from "redux-devtools-extension";

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

export default store;

여기까지 redux-devtools-extension으로 데이터를 보낼 준비가 되었습니다.

확인해보기

redux-devtools-extension의 github주소
https://github.com/zalmoxisus/redux-devtools-extension

여기서 각각 크롬, 파이어폭스, 일렉트론 등에서 설치하는 방법이 나와있습니다.

크롬 웹 스토어에서 확장프로그램으로 설치할 수 있습니다.

설치 후 크롬 개발자도구에서 확인해 보겠습니다.

우리 애플리케이션에서 일어나고 있는 모든 스토어의 변화에 대해서 왼쪽 창에 등록이 되어 있습니다.
오른쪽 창에서는 왼쪽에서 선택한 액션에 대한 정보를 확인할 수 있습니다.
우리가 직접 여기서 액션을 dispatch를 해볼수도 있습니다.
아래창에서 recoding과 debuging 기능도 사용해 볼 수 있습니다.

redux에 문제가 있다고 판단될 때 이 redux-devtools-extension를 이용하여 디버깅을 합니다.

다음 시간에는 다음 middleware를 사용해보겠습니다.

redux-thunk

redux의 미들웨어 중에서 가장 많이 쓰이는 라이브러리인 redux-thunk에 대해서 배워보겠습니다.
thunk: (떵크 or 청크)

https://github.com/reduxjs/redux-thunk

개요

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

우리의 프로젝트에 redux-thunk를 설치하고, 우리의 비동기 로직을 redux-thunk로 변경해보도록 하겠습니다.

설치

npm i redux-thunk

기본 설정

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

const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)));

export default store;
  1. import thunk from "redux-thunk" import 합니다.
  2. applyMiddleware()에 import로 가져온 thunk함수를 middleware 인자로 넣습니다.

이대로 실행해도 아무 변화도 없습니다. 왜냐하면
thunk함수는 액션 생성자함수를 리턴할 때에만 반응합니다. 액션 객체를 return 할때에는 기존 동작처럼 동작합니다.

여기까지 middleware 설정은 끝났습니다.

store에 기본 세팅은 끝났으니 thunk를 동작시키기위해 action 생성자 함수를 수정하기

앞서 우리가 만든 컴포넌트중 비동기로 데이터를 가져와서 List를 출력하는 컴포넌트가 있었고, 이 컴포넌트의 뷰를 처리하는 로직과 비동기로 데이터를 받아오는 로직(HOC)으로 파일 단위로 분리하였습니다.(UserList.jsx, UserListContainer.jsx)

비동기로직이 있는 컴포넌트로 가서..

// UserListContainer.jsx

import UserList from "../components/UserList";
import { useSelector, useDispatch } from "react-redux";
import { useCallback } from "react";
import { getUsersStart, getUsersSuccess, getUsersFail } from "../redux/actions";
import axios from "axios";

export default function UserListContainer() {
  console.log("UserListContainer start");
  const users = useSelector((state) => state.users.data);
  const dispatch = useDispatch();

  const getUsers = useCallback(async () => {
    try {
      dispatch(getUsersStart());
      const res = await axios.get("https://api.github.com/users");
      dispatch(getUsersSuccess(res.data));
    } catch (error) {
      dispatch(getUsersFail(error));
    }
  }, [dispatch]);

  return <UserList users={users} getUsers={getUsers} />;
}

getUsers 안에서 비동기 로직을 처리하고 있습니다.
getUsers 함수를 액션 생성 함수로 바꾸고, 함수를 리턴하는 방식으로 수정해보겠습니다.

먼저 action.js로 가서 (기존처럼 액션 객체가 아닌) 함수를 리턴하는방식으로 액션 생성 함수를 추가하겠습니다.

// action.js

export function getUsersThunk() {
  return async (dispatch) => {
    try {
      dispatch(getUsersStart());
      const res = await axios.get("https://api.github.com/users");
      dispatch(getUsersSuccess(res.data));
    } catch (error) {
      dispatch(getUsersFail(error));
    }
  };
}

함수를 리턴할 때 그냥 함수가 아닌 함수의 인자로 dispatch를 넘겨줍니다.
우리는 dispatch를 보통 컨테이너쪽에서 처리하고 있었습니다. 하지만 이제는 액션을 생성하는 쪽에서 할 수 있도록 dispatch를 여기로 가지고 들어온 것입니다.

이제 다시 비동기 로직이 있었던 컨테이너쪽에서는 위에서 만든 비동기 함수를 리턴하고 있는 함수를 dispatch로 보내는 작업만 해주면됩니다.

// UserListContainer.jsx

import UserList from "../components/UserList";
import { useSelector, useDispatch } from "react-redux";
import { useCallback } from "react";
import { getUsersThunk } from "../redux/actions";

export default function UserListContainer() {
  console.log("UserListContainer start");
  const users = useSelector((state) => state.users.data);
  const dispatch = useDispatch();

  // 기존 비동기 코드
  // const getUsers = useCallback(async () => {
  //   try {
  //     dispatch(getUsersStart());
  //     const res = await axios.get("https://api.github.com/users");
  //     dispatch(getUsersSuccess(res.data));
  //   } catch (error) {
  //     dispatch(getUsersFail(error));
  //   }
  // }, [dispatch]);

  // thunk를 반응하게 만들 코드
  const getUsers = useCallback(() => {
    dispatch(getUsersThunk());
  }, [dispatch]);

  return <UserList users={users} getUsers={getUsers} />;
}

이렇게 하고 실행하면 큰 문제없이 잘 됩니다.

기존에는 비동기로직이 컨테이너에 있었는데, 이제는 액션을 다루는
액션생성함수에서 처리할 수 있게 됩니다. 그리고 컨테이너는 그냥 액션생성자를 컴포넌트에 전달하는 역할만하게 됩니다.
이렇게하면 실제로 dispatch되는 로직들은 전부 다 액션에서 관리가 되기 때문에 관심사가 적절히 분리됩니다.

그럼 thunk은 어떻게 이런 일을 할 수 있을까요?

thunk코드 살펴보기

github의 thunk 코드를 보면 매우 간단하게 작성되어 있습니다.
간단한 코드이지만 이 코드가 우리의 비동기 로직을 수행하는 것을 편하게 도와줍니다.

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    
    return next(action);
  }
}

const thunk = createThunkMiddleweare();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

많이 쓰이고, 이 라이브러리를 통해서 redux의 비동기 작업이 많이 되고 있습니다.

redux-promise-middleware

이번에는 비동기 처리를 위한 또 다른 미들웨어인 redux-promise-middleware를 사용해보겠습니다.

0개의 댓글