NEXTJS 로 twitter클론 해보기(8. Redux middleware )

26821 단어 nextjsReactReact

리덕스 미들웨어

리덕스 미들웨어를 사용하면 액션이 디스패치 된 다음, 리듀서에서 해당 액션을 받아와서 업데이트하기 전에 추가적인 작업을 할 수 있습니다.

미들웨어 만들어보고 이해하기

리덕스 미들웨어의 기본 템플릿

const middleware = store => next => action => {
  // 하고 싶은 작업...
}

첫번째 store는 리덕스 스토어 인스턴스입니다. 이 안에 dispatchgetStatesubscribe 내장함수들이 들어있습니다.

두번째 next 는 액션을 다음 미들웨어에게 전달하는 함수입니다. next(action) 이런 형태로 사용합니다. 만약 다음 미들웨어가 없다면 리듀서에게 액션을 전달해줍니다. 만약에 next 를 호출하지 않게 된다면 액션이 무시처리되어 리듀서에게로 전달되지 않습니다.

세번째 action 은 현재 처리하고 있는 액션 객체입니다.

미들웨어 직접 작성해보기

logger middleware

const myLogger = store => next => action => {
  console.log(action); // 먼저 액션을 출력합니다.
  const result = next(action); // 다음 미들웨어 (또는 리듀서) 에게 액션을 전달합니다.
  return result; // 여기서 반환하는 값은 dispatch(action)의 결과물이 됩니다. 기본: undefined
};

export default myLogger;

thunk middleware

액션이 함수 타입이면 해당 함수의 인자에 store.dispatch, store.getState을 넣어줍니다. 이렇게하면

해당 함수에서 dispatch를 쓸수 있고 이말은 한번의 액션으로 dispatch를 여러번 할 수 있게 해준다는 것입니다.

const thunk = store => next => action =>
  typeof action === 'function'
    ? action(store.dispatch, store.getState)
    : next(action)

redux-saga 사용해보기

설치

npm i redux-saga

적용

store/configureStore.js

import { createWrapper } from "next-redux-wrapper";
import { applyMiddleware, compose, createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import createSagaMiddleware from "redux-saga";
import reducer from "../reducers/index";
import rootSaga from "../sagas/index";

const configureStore = () => {
  const sagaMiddleware = createSagaMiddleware(); //sagamiddleware만들기
  const middlewares = [sagaMiddleware]; //sagamiddleware넣어주기 
  const enhancer =
    process.env.NODE_ENV === "production"
      ? compose(applyMiddleware(...middlewares))
      : composeWithDevTools(applyMiddleware(...middlewares));
  const store = createStore(reducer, enhancer); //앱의 상태를 유지하는 Redux 스토어를 만듭니다
  store.sagaTask = sagaMiddleware.run(rootSaga);
  return store;
};

const wrapper = createWrapper(configureStore, {
  debug: process.env.NODE_ENV === "development",
});

export default wrapper;

sages/index.js 파일 만들기

먼저 rootSaga를 만들줍니다.

redux-saga/effects에 있는 fork를 통해 argument에 들어있는 함수를 실행시켜줍니다.

all은 여러개의 함수넣을때 사용합니다.

export default function* rootSaga() {
  yield all([
    //all은 배열안에 들어가 있는것을 동시에(한방에?) 실행시킨다.
    fork(watchLogin), //fork는 인자에 들어가있는 함수를 실행시킨다.
    fork(watchLogOut),
    fork(watchAddPost),
  ]);
}

rootSaga를 만든후 fork에 들어갈 함수를 정의해 줍니다.

take는 첫번째 argument에들어가는 액션이 실행될때까지 기다리겠다 라는 뜻입니다. (동기)

해당 액션이 실행되면 두번째 argument가 실행됩니다.

하지만 문제가 하나 있습니다. take는 해당 액션이 한번 실행되면 그다음엔 실행을 안합니다.

즉 딱 한번만 실행된다는거죠 이를 해결하기위해 while(true)를 사용해도 되지만 Saga에 있는 takeEvery(비동기)와 takeLatest를 사용하면 해결됩니다.

takeEvery는 말그대로 딱한번만 실행되는걸 계속 실행돠게 만들어주고

takeLatest는 계속 실행되게 만들어 줄 뿐만 아니라 사용자가 실수로 액션을 연속적으로 2번 눌렀을때를 대비하여 마지막 액션만 실행하게 해줍니다.

function* watchLogin() {
  //take는 LOG_IN_REQUES이라는 액션이 실행될때까지 기다리겠다 라는 뜻 (동기)
  //LOG_IN_REQUES이라는 액션이 실행되면 두번째 argument가 실행된다.
	//일회용 딱 한번만 실행된다.
  yield take("LOG_IN_REQUEST", login);
}

call역시 argument에 들어간 함수를 실행시킵니다. fork와 차이점은 call은 동기 fork는 비동기 입니다.

call의 두번째 argument값으로는 첫번째 argument로 넣어준 함수의 argument로 사용됩니다.

put을 통해 argument에 넣어준 action을 실행시켜줍니다

function loginAPI(data) {
  return axios.post("/api/login", data);
}

function* login(action) {
  try {
    const result = yield call(loginAPI, action.data); //call fork 차이 fork는 비동기 call은 동기
    yield put({
      type: "LOG_IN_SUCCESS",
      data: result.data,
    });
  } catch (err) {
    yield put({
      //put은 dispatch라고 생각하면 된다.
      type: "LOG_IN_FAILURE",
      data: err.response.data,
    });
  }
}

redux-saga파일 분리하기

sagas

index.js → 분리된 saga를 합쳐준다.

user.js → user에 관련된 saga만 적어준다.

post.js → post에 관련된 saga만 적어준다.

sagas/index.js

import { all, fork } from "redux-saga/effects";
import postSaga from "./post";
import userSaga from "./user";

export default function* rootSaga() {
  yield all([
    fork(postSaga),
    fork(userSaga),
  ]);
}

sagas/user.js

import { all, fork, delay, takeLatest, put } from "redux-saga/effects";
import axios from "axios";

//이부분은 제너레이터 아니다.
// 서버 요청 부분
function loginAPI(data) {
  return axios.post("/api/login", data);
}

function* login(action) {
  try {
    console.log("saga Login");
    // const result = yield call(loginAPI, action.data); //call fork 차이 fork는 비동기 call은 동기
    yield delay(1000);
    yield put({
      type: "LOG_IN_SUCCESS",
      data: action.data,
    });
  } catch (err) {
    yield put({
      //put은 dispatch라고 생각하면 된다.
      type: "LOG_IN_FAILURE",
      data: err.response.data,
    });
  }
}

function logoutAPI() {
  return axios.post("/api/logout");
}

function* logout() {
  try {
    //const result = yield call(logoutAPI); //call fork 차이 fork는 비동기 call은 동기
    yield delay(1000);
    yield put({
      type: "LOG_OUT_SUCCESS",
    });
  } catch (err) {
    yield put({
      //put은 dispatch라고 생각하면 된다.
      type: "LOG_OUT_FAILURE",
      data: err.response.data,
    });
  }
}
function* watchLogin() {
  //take는 LOG_IN_REQUES이라는 액션이 실행될때까지 기다리겠다 라는 뜻 (동기)
  //LOG_IN_REQUES이라는 액션이 실행되면 두번째 argument가 실행된다.
  //일회용 딱 한번만 실행된다.
  //takeEvery는 비동기
  console.log("takeLatest");
  yield takeLatest("LOG_IN_REQUEST", login);
}

function* watchLogOut() {
  yield takeLatest("LOG_OUT_REQUEST", logout);
}

export default function* userSaga() {
  yield all([
    fork(watchLogin), //fork는 인자에 들어가있는 함수를 실행시킨다.
    fork(watchLogOut),
  ]);
}

좋은 웹페이지 즐겨찾기