미들웨어란
리덕스를 사용하면서 비동기 작업을 다룰 때는 미들웨어가 있어야 더욱 손쉽게 상태를 관리 할 수 있습니다. 미들웨어는 액션이 디스패치 되면서 reducer로 전달하는 그 중간지로 생각하면 된다. 즉, dispatch 되어서 리듀서에서 이를 처리하기전에 사전에 지정된 작업들을 처리한다고 생각하면 된다.
미들웨어 만들어보기
삼단함수가 미들웨어다 삼단함수 모양이 미들웨어 형식이다.
const middleware = (store) => (next) => (action) => { // 여기 앞뒤로 action 전에 기능을 추가 할 수 있다. next(action); //여기 앞뒤로 action 후에 기능을 추가 할 수 있다. }; // 구조 분해 할당화 하면 const middleware = ({dispatch, getState}) => (next) => (action) => {}
이제 실제 thunkmiddleware를 만들어 보면 다음과 같다.
const thunkMiddleware = (store) => (next) => (action) => { if (typeof action === "function") { // 비동기 return action(store.dispatch, store.getState); } console.log("test", action); return next(action); // 동기 };
실제 redux-thunk와 차이점이 없는 코드이다 redux-thunk 레포를 들어가면 너무 간단한 코드여서 놀랄것이다.
applyMiddleware
store에 middleware를 적용할때에는 applyMiddleware 함수를 사용한다.
const store = createStore( reducer, initialState, applyMiddleware(thunkMiddleware) );
또한 한개의 미들웨어가 아닌 여러개의 미들웨어를 연결할 수 도 있다.
const store = createStore( reducer, initialState, applyMiddleware(middleware,thunkMiddleware) );
compose를 사용해 enhancer를 만들어보자
사실 enhancer를 만들어 store에 연결하는게 좋다. compose는 순차적으로 함수를 적용해 나가는 기능을 가지고 있다.
const enhancer = compose(applyMiddleware(middleware,thunkMiddleware)) const store = createStore( reducer, initialState, enhancer );
실제 사용
// action에서 코드이다. setTimeout을 사용해 비동기처리를 추가 export const logIn = (data) => { // async action creator return (dispatch, getState) => { // async action dispatch(logInRequest(data)); try { setTimeout(() => { dispatch( logInSuccess(data) ); }, 2000); } catch (e) { dispatch(logInFailure(e)); } }; };
// reduecer 코드이다. import produce from "immer"; const initialState = { isLoggingIn: false, data: null, }; export const userReducer = (prevState = initialState, action) => { return produce(prevState, (draft) => { switch (action.type) { case "LOGINREQUEST": draft.data = action.data; draft.isLoggingIn = true; break; case "LOGINSUCCESS": draft.data = action.data; draft.isLoggingIn = false; break; case "logInFailure": draft.data = action.data; draft.isLoggingIn = true; case "LOGOUT": draft.data = null; default: break; } }); };
const store = createStore( reducer, initialState, applyMiddleware(thunkMiddleware) );
비동기처리 미들웨어를 만들고 store에 연결 후 action에 비동기처리 코드를 추가 시켜 미들웨어의 기능을 실행시켜주었고 reducer에는 각 action의 타입별로 store의 state를 저장해주게끔 만들어 dispatch(logIn(data))를 해줌으로써 위 사진과 같은 결과를 얻을 수 있다.
실제 따라해보면 LOGINSUCCESS type은 비동기 settimeout이 적용된 것을 볼 수 있을 것 이다.
useDispatch & useSelector
react-redux에서 제공하는 hook으로 일단 아래 명령어를 터미널에 입력.
npm i react-redux
두가지 hook은 connect함수, createAction을 사용하지 않고 useSelector를 통해 state에 접근할 수 있고 useDispatch로 생성한 action을 dispatch 할 수 있다.
기본세팅(연결)
import React from "react"; import ReactDOM from "react-dom"; import { Provider } from "react-redux"; import { store } from "./store"; import App from "./App"; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.querySelector("#root") );
react-redux Provider를 가져오고 redux를 이용해서 만든 store를 연결해준다. 그리고 react 컴포넌트인 app을 연결해준다. 이렇게 되면 react-redux redux react 세개를 연결해 준 것이다.
사용해보기
import React, { useCallback, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; //react-redux에서 hook등을 가지고 온다. import { logIn, logOut } from "./components/actions/User"; //dispatch 할 action들을 가지고 온다. const App = () => { const user = useSelector((state) => state.user); //store에 저장하고 있는 state에 user로 접근해 //user 상수로 관리하겠다는 코드 이다. const posts = useSelector((state) => state.posts); const dispatch = useDispatch(); //생성한 action을 useDispatch를 통해 발생시킬 수 있다. const onClick = () => { dispatch(logIn({ id: 1, nickname: "안녕" })); }; const onLogout = () => { dispatch(logOut()); }; return ( <div> {user.isLoggingIn ? ( <div>로그인 중</div> ) : user.data ? ( <div>{user.data.nickname}</div> ) : ( "로그인 해주세요." )} {!user.data ? ( <button onClick={onClick}>로그인</button> ) : ( <button onClick={onLogout}>로그아웃</button> )} </div> // useSelector로 store에 state를 가져와 Logging에 대한 핸들링 // data에 유무에 따라 nickname이 보이고 버튼이 달라지게끔 핸들링 ); }; export default App;