[React] react-redux 비동기 처리 - thunk, saga

GONI·2021년 10월 8일
0
post-thumbnail

- redux

react-redux에서 로그인과 같은 비동기 작업을 해야 할 때는

const loginUser = useCallback(async () => {
  try {
    dispatch(loginStart())
    const res = await axios.get("/loginexample")
    dispatch(loginSuccess(res.data))
  } catch(err) {
    dispatch(loginFail(err))
  }
}, [dispatch] )

위 흐름과 같이 진행됐어야 했다. 하지만 redux-thunk라는 미들웨어를 통해 비동기 작업을 조금 더 편리하게 관리할 수 있다.


- redux-thunk

./redux-action.js 파일에서 thunk 액션을 생성해 준다

export function loginUserThunk() {
  return async (dispatch) => {
    try {
      dispatch(loginStart())
      const res = await axios.get("/loginexample")
      dispatch(loginSuccess(res.data))
    } catch(err) {
      dispatch(loginFail(err))
    }
  }
}

이후 해당 액션을 사용하고자 하는 곳에 불러온 뒤,

const loginUser = useCallback(() => {
  dispatch(loginUserThunk())
}, [dispatch] )

위 코드와 같이 사용하면 redux에서 비동기 액션을 사용함과 실제 액션을 사용하는 곳에는 훨씬 간결하게 코드를 작성할 수 있는 것을 볼 수 있다.


참고 - redux-thunk에서 router 사용하기 : connected-react-router

history.js 파일을 생성한다. 파일을 따로 생성하는 이유는 여러 곳에서 history파일을 불러와야 하기 때문이다.

import { createBrowserHistroy } from "history"
const history = createBrowserHistroy()

export default history

store에 import 해준 뒤, middleware로 적용한다.
또한 routerMiddleware를 import한 뒤 이곳에도 history를 적용해준다.

import thunk from "redux-thunk"
import { routerMiddleware } from "connected-react-router"
import history = "./history"

const store = createStore(
  rootReducer,
  applyMiddleware(
    ReduxThunk.withExtraArgument({ history }),
    routerMiddleware(history),
  )
)

다음으로 rootReducer에 router를 결합해준다.

import { connectRouter } from "connected-react-router"
import history from "../history"

const rootReducer = combineReducers({
	...
	router: connectRouter(history),
})

이제 마지막으로 App.js파일에서 history를 불러와 적용한다.

import { ConnectedRouter } from "connected-react-router"
import history = "./history"

<ConnectedRouter history={history}>
  <Route />
  <Route />
  <Route />
</ConnectedRouter>

기존 BrowserRouter에서 ConnectedRouter로 바뀐 것을 잘 확인해야 한다.

이렇게하면 router를 사용하기 위한 세팅이 완료된다.
그 후 thunk에 적용하기 위해서 분해할당을 한 뒤,

export function loginUserThunk() {
  return async (dispatch, getState, { history }) => {
    try {
      dispatch(loginStart())
      const res = await axios.get("/loginexample")
      dispatch(loginSuccess(res.data))
      history.push("/")
    } catch(err) {
      dispatch(loginFail(err))
      history.push("/login")
    }
  }
}

기존 react-router에서 사용하던 방식으로 적용해 주면 redux-thunk혹은 redux-saga에서도 redux sate와 강하게 결합된 router를 사용할 수 있다.


- redux-saga

redux-sage역시 비동기 작업을 할 때 유용하며, generator를 사용한다.

function* loginUserSaga(action) {
    try {
      yield put(login_start())
      const res = yield call(axios.get, "/loginexample")
      yield put(loginSuccess())
    } catch(err) {
      yield put(loginFail(err)) 
    }
}

다음은 해당 saga에 대한 액션 타입을 정의해주고,

const LOGIN_USER = "LOGIN_USER"

export function* userSaga() {
  yield takeEvery(LOGIN_USER, loginUserSaga)
}

코드에서 실제 불러오는 액션 실행 함수를 생성해준다.

export function loginUser() {
  return {
    type: LOGIN_USER
  }
}

마지막으로 rootReducr처럼 모든 saga를 불러오는 rootSaga를 생성해준다.

export default function* rootSaga() {
  yield all([userSaga()])
}

정말 마지막으로 (찐막) redux-saga미들웨어를 생성해 준 곳에서 rootSaga를 실행해주면

const createSagaMiddleware from "redux-saga

sagaMiddleware.run(rootSaga)

redux-saga를 어느 코드에서나 사용 가능하다!

const loginUsers = useCallback(() => {
  dispatch(loginUser())
}, [dispatch] )

-> redux-thunk를 redux-saga로 교체


아직은 사용하기에 좀 낯설었던 redux-thunk와 redux-saga를 정리해 보면서 redux를 어느정도 자유자재로 사용할 수 있다면 thunk와 saga는 나름대로 금방 이해할 수 있겠다는 생각을 했다. 물론 직접 사용하여 코드에 적용해보는 것 만큼 빠르게 이해할 수 있는 방법을 없겠지만,,,

앞으로 redux를 사용하면서 비동기 작업을 실행할 때 두 가지의 미들웨어를 사용하는 습관을 가져봐야겠다

profile
오로지 나의 기억력을 위한 일지

0개의 댓글