이전 글에서 동기적으로 임시 구현한 로그인 기능을 비동기적으로 구현합니다.
편의상 setTimeout으로 서버 API mocking 합니다.
Server API
/src/server/index.js
setTimeout으로 1초 뒤에 로그인 성공/실패 결과를 반환하도록 구현하였습니다.
// ...
// 로그인 요청
function logInRequest(id, password) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id === ID && password === PASSWORD) {
// 로그인 성공 시 nickname 반환
resolve({ nickname: 'devdev' });
}
reject('Invalid ID/Password. Try again.');
}, 1000);
});
}
// 로그아웃 요청
function logOutRequest() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('success'), 1000);
});
}
// ...
Middleware
앞선 글에서 언급했듯이, 순수 redux는 동기 처리만 가능합니다.
따라서 비동기를 처리하기 위한 middleware가 필요합니다.
이번에는 redux-thunk를 사용합니다.
redux-thunk의 컨셉은 실제 코드를 보면 쉽게 파악할 수 있습니다.
출처: https://github.com/reduxjs/redux-thunk/blob/master/src/index.ts
// ...
function createThunkMiddleware<
State = any,
BasicAction extends Action = AnyAction,
ExtraThunkArg = undefined
>(extraArgument?: ExtraThunkArg) {
// Standard Redux middleware definition pattern:
// See: https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware
const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =
({ dispatch, getState }) =>
next =>
action => {
// The thunk middleware looks for any functions that were passed to `store.dispatch`.
// If this "action" is really a function, call it and return the result.
if (typeof action === 'function') {
// Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
return action(dispatch, getState, extraArgument)
}
// Otherwise, pass the action down the middleware chain as usual
return next(action)
}
return middleware
}
export const thunk = createThunkMiddleware()
// ...
기본적으로 redux action은 객체이지만, redux-thunk는 action이 함수인 경우 이를 실행한 결과를 반환합니다.
이 때 dispatch와 getState를 action 함수의 파라미터로 넘겨주어 action 함수 내부에서 비동기적인 dispatch가 가능하도록 합니다.
/src/redux/module/user.js
비동기 처리를 위한 action creator는 다음과 같이 dispatch와 getState를 파라미터로 받아 내부적으로 dispatch를 실행하는 형태로 작성하게 됩니다.
// ...
// Action Creators
export function logIn(id, password) {
return (dispatch, getState) => {
dispatch({ type: LOG_IN_PENDING });
const response = logInRequest(id, password);
response
.then((res) => dispatch({ type: LOG_IN_SUCCESS, payload: res }))
.catch((error) => dispatch({ type: LOG_IN_REJECTED, payload: error }));
};
}
// ...
로그인이 비동기적으로 이루어짐에 따라 대기, 성공, 실패 세 단계의 상태가 필요하므로 이에 맞게 user 모듈의 reducer를 다시 작성해줍니다. (전체 코드는 글 맨 아래의 링크를 참고)
//...
export const LogInState = { IDLE: 0, PENDING: 1, REJECTED: 2 };
// Reducer
const initialState = { data: null, logInState: LogInState.IDLE, error: '' };
export default function reducer(state = initialState, action) {
switch (action.type) {
case LOG_IN_PENDING: {
return {
...state,
logInState: LogInState.PENDING,
error: '',
};
}
case LOG_IN_SUCCESS: {
return {
...state,
data: action.payload,
logInState: LogInState.IDLE,
error: '',
};
}
// ...
}
src/redux/index.js
redux-thunk를 사용하기 위해 applyMiddleware(thunk) 코드를 추가합니다.
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './module';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
비동기 처리를 기다리는 UI를 LogInComponent에 추가하면 비동기 처리가 완료됩니다. (맨 밑 링크 참조)
결과
로그인 버튼 클릭 후 1초 뒤 로그인이 성공되는 것을 확인할 수 있습니다.
소스 코드
GitHub repo: https://github.com/dev-wann/redux-practice, my-todo 폴더 내부 내용
이번 글에 해당하는 커밋: https://github.com/dev-wann/redux-practice/commit/fdf5ea92dd16b15c8a7b153b7d90c6f7e1adb205
'Web development > 상태관리' 카테고리의 다른 글
Redux - 로그인 + TodoList 예제 구현 (4) - RTK (0) | 2023.11.16 |
---|---|
Redux - 로그인 + TodoList 예제 구현 (3) redux-thunk (0) | 2023.11.15 |
Redux - 로그인 + TodoList 예제 구현 (1) redux (0) | 2023.11.12 |
Redux - 기본 개념 (0) | 2023.11.02 |