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-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-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를 사용하면서 비동기 작업을 실행할 때 두 가지의 미들웨어를 사용하는 습관을 가져봐야겠다