[React] MiddleWare

youngseo·2022년 8월 12일
0

REACT

목록 보기
44/52
post-thumbnail

MiddleWare

프로그래밍에서 비동기 처리와 전역상태관리는 굉장히 중요합니다. 또한 그만큼 어렵습니다.

로그인한 유저 정보를 전역상태로 관리를 해야하는 경우를 예시로 본다면 로그인요청 - 서버 응답- 응답 받은 정보를 전역상태관리와 같은 순서로 진행되게 됩니다.

현재 서버응답까지는 비동기처리와 관련한 로직, 그 이후는 전역상태관련한 로직이 필요합니다.

이러한 상황이 주어졌을 때 어떻게 구현하는 것이 좋을까요?

방법1

컴포넌트의 useEffect내에서 API호출을 하고, 응답받은 결과를 스토어에 없데이트한다.

dispatch({ type: "updateUser" , payload: { nickname: 김코딩, purchased: **패키지 }})

### 방법2

컴포넌트의 useEffect 내에서 dispatch({ type: “updateUser”}) 로 액션 객체만을 보내고, user Store의 reducer 안에서 API 를 호출하고, 응답 받은 결과를 스토어에 업데이트 한다

=> reducer는 순수함수 이기 때문에 방법2는 불가능합니다.
(순수함수: 동일한 인자가 주어졌을 때 항상 동일한 결과를 반환하는 함수)

하지만 방법1의 경우 가능하지만 컴포넌트 내에서 처리해야할 일이 너무 많습니다. (컴포넌트 내에서 처리하는 비즈니스로직이 너무 많습니다.)

비동기 통신을 위한 redux-middleware

이러한 단점을 보완하기 위해 사용할 수 있는 것이 바로 middleware라는 개념입니다.

액션객체를 dispatch를 한 후 바로 reducer에 보내지 않고 비동기처리, 로딩처리 등의 작업을 middleware에서 진행을 한 후 reducer로 보내게 됩니다.

또한 이 middleware를 사용하면 컴포넌트에서 비즈니스 로직도 구분해 낼 수 있습니다.

대표적인 middleware라이브러리로는 redux chunk, reudx saga, redux-observable가 있습니다.

createAsyncThunk

리덕스 툴깃에 createAsyncThunk라는 함수가 있습니다. 이를 사용하면 다른 middleware를 사용하지 않아도 비동기통신을 편리하게 사용을 할 수 있습니다.

실습

1. 설치

$ npx create-react-app my-redux-app
$ yarn add redux react-redux
$ yarn add redux-thunk

2. index.js

import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
import rootReducer from "./modules/index";
import App from "./App";

//createStore을 할 때 어떤 middleware를 사용할 것인지도 함께 명시해줍니다.
const store = createStore(rootReducer, applyMiddleware(thunk));

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

3. modules

index.js

import { combineReducers } from "redux";
import account from "./account/account";

const rootReducer = combineReducers({ account });

export default rootReducer;

account>account.js

import { fetchUser } from "./api";

// 액션
const FETCH_USER_REQUEST = "FETCH_USER_REQUEST";
const FETCH_USER_SUCCESS = "FETCH_USER_SUCCESS";
const FETCH_USER_FAILURE = "FETCH_USER_FAILURE";

// 액션 생성 함수
export const fetchUserRequest = () => ({
  type: FETCH_USER_REQUEST,
});

export const fetchUserSuccess = ({ name, email }) => ({
  type: FETCH_USER_SUCCESS,
  payload: { name, email },
});

export const fetchUserFailure = () => ({
  type: FETCH_USER_FAILURE,
});

// TODO: thunk 함수 만들기
export const fetchUserThunk = () => {};

const initialState = {
  loading: false,
  name: "",
  email: "",
};

export default function counter(state = initialState, action) {
  switch (action.type) {
    case FETCH_USER_REQUEST:
      return {
        ...state,
        loading: true,
      };
    case FETCH_USER_SUCCESS:
      return {
        loading: false,
        name: action.payload.name,
        email: action.payload.email,
      };
    case FETCH_USER_SUCCESS:
      return initialState;
    default:
      return state;
  }
}

api.js

export const fetchUser = () => {
  return new Promise((resolve) => {
    setTimeout(
      () => resolve({ name: "hwarari", email: "hwarari@gmail.com" }),
      2000
    );
  });
};

4. App.js

import { useSelector, useDispatch } from "react-redux";
import { fetchUser } from "../src/modules/account/api";
import {
  fetchUserRequest,
  fetchUserSuccess,
  fetchUserFailure,
} from "./modules/account/account";

function App() {
  const account = useSelector((state) => state.account);
  const { loading, name, email } = account;
  const dispatch = useDispatch();

  const handleClick = async () => {
    dispatch(fetchUserRequest());
    try {
      const res = await fetchUser();
      dispatch(fetchUserSuccess({ name: res.name, email: res.email }));
    } catch {
      dispatch(fetchUserFailure());
    }
  };

  return (
    <div className="App">
      <button onClick={handleClick}>User 정보 가져오기</button>
      {loading ? (
        <p>loading...</p>
      ) : name && email ? (
        <>
          <p>이름 : {name}</p>
          <p>이메일 : {email}</p>
        </>
      ) : null}
    </div>
  );
}

export default App;

0개의 댓글