redux-saga는 액션들을 모니터링하고, 특정 액션이 발생시 이 액션을 처리하기 위한 작업을 수행합니다. 우선 redux-saga, axios를 설치하도록 하겠습니다. axios는 API를 연동하기 위한 라이브러리입니다.
npm install redux-saga axios
import axios from 'axios';
const client = axios.create();
export default client;
프록시를 8900번 포트(apigateway-service)로 설정하여 CORS 오류를 예방하도록 하겠습니다.
{
...
"proxy": "http://localhost:8900/"
}
다음과 같이 회원 관리에 필요한 api를 작성하도록 하겠습니다. 기존에 만들어둔 auth-service의 endpoint url을 활용합니다.
import client from './client';
export const login = ({
email,
password
}) => client.post('/auth-service/login', {
email,
password
});
export const register = ({
email,
password,
nickname,
phoneNumber
}) => client.post('/auth-service/register', {
email,
password,
nickname,
phoneNumber
});
export const getUser = ({ userId }) => client.getUser('/auth-service/:userId/getUser', { userId });
이제 loading 리덕스 모듈을 만들어 액션함수를 시작하고 종료되는 시점을 나타내주도록 하겠습니다.
import { createAction, handleActions } from "redux-actions";
const START_LOADING = 'loading/START_LOADING';
const FINISH_LOADING = 'loading/FINISH_LOADING';
export const startLoading = createAction(
START_LOADING,
requestType => requestType,
);
export const finishLoading = createAction(
FINISH_LOADING,
requestType => requestType,
);
const initialState = {};
const loading = handleActions(
{
[START_LOADING]: (state, action) => ({
...state,
[action.payload]: true,
}),
[FINISH_LOADING]: (state, action) => ({
...state,
[action.payload]: false,
}),
},
initialState,
);
export default loading;
import { combineReducers } from "redux";
import auth from './auth';
import loading from './loading';
const rootReducer = combineReducers(
{
loading,
auth,
},
);
export default rootReducer;
createRequestSaga는 제네레이터 함수를 이용하여 액션 함수가 정상적으로 작동이 되었을 때, API의 데이터에 대한 요청과 응답을 처리하기 위한 유틸함수입니다.
import { call, put } from "@redux-saga/effects";
import { startLoading, finishLoading } from "../modules/loading";
export const createRequestActionTypes = type => {
const SUCCESS = `${type}_SUCCESS`;
const FAILURE = `${type}_FAILURE`;
return [type, SUCCESS, FAILURE];
};
export default function createRequestSaga(type, request) {
const SUCCESS = `${type}_SUCCESS`;
const FAILURE = `${type}_FAILURE`;
return function*(action) {
yield(put(startLoading(type)));
try {
const response = yield call(request, action.payload);
yield put({
type: SUCCESS,
payload: response.data,
});
} catch(e) {
yield put({
type: FAILURE,
payload: e,
error: true,
});
}
yield put(finishLoading(type));
};
}
1) createRequestActionTypes: 액션타입에 대한 코드입니다. 예를 들어 로그인에 관한 액션 타입을 'auth/LOGIN', 'auth/LOGIN_SUCCESS', 'auth/LOGIN_FAILURE'라고 했을 때 해당 메서드를 사용하여 액션타입을 변수에 부여합니다.
2) createRequestSaga: Saga를 요청하는 코드입니다. 예를 들어 'auth/LOGIN'이라는 타입과 아이디, 비밀번호를 가진 ./src/lib/api/auth.js에 존재하는 login request가 들어왔을 때, 이에 대한 요청을 제네레이터 함수를 이용하여 처리합니다.
그러면 이 코드들을 이용해서 api처리를 위한 코드를 완성하도록 하겠습니다.
import { createAction, handleActions } from "redux-actions";
import produce from 'immer';
import { takeLatest } from "@redux-saga/core/effects";
import createRequestSaga,{
createRequestActionTypes
} from "../lib/createRequestSaga";
import * as authAPI from '../lib/api/auth';
const CHANGE_FIELD = 'auth/CHANGE_FIELD';
const INITIALIZE_FORM = 'auth/INITIALIZE_FORM';
const [LOGIN, LOGIN_SUCCESS, LOGIN_FAILURE] = createRequestActionTypes('auth/LOGIN');
const [REGISTER, REGISTER_SUCCESS, REGISTER_FAILURE] = createRequestActionTypes('auth/REGISTER');
export const changeField = createAction(
CHANGE_FIELD, ({
form,
key,
value
}) => ({
form,
key,
value,
}),
);
export const initializeForm = createAction(INITIALIZE_FORM, form => form);
export const login = createAction(LOGIN, ({
email,
password
}) => ({
email,
password
}));
export const register = createAction(REGISTER, ({
email,
password,
nickname,
phoneNumber
}) => ({
email,
password,
nickname,
phoneNumber
}));
const loginSaga = createRequestSaga(LOGIN, authAPI.login);
const registerSaga = createRequestSaga(REGISTER, authAPI.register);
export function* authSaga() {
yield takeLatest(LOGIN, loginSaga);
yield takeLatest(REGISTER, registerSaga);
}
const initialState = {
register: {
email: '',
password: '',
passwordConfirm: '',
nickname: '',
phoneNumber: '',
},
login: {
email: '',
password: '',
},
auth: null,
authError: null,
};
const auth = handleActions(
{
[CHANGE_FIELD]: (state, { payload: { form, key, value }}) =>
produce(state, draft => {
draft[form][key] = value;
}),
[INITIALIZE_FORM]: (state, { payload: form }) => ({
...state,
[form]: initialState[form],
authError: null,
}),
[LOGIN_SUCCESS]: (state, { payload: auth }) => ({
...state,
authError: null,
auth
}),
[LOGIN_FAILURE]: (state, { payload: error }) => ({
...state,
authError: error,
}),
[REGISTER_SUCCESS]: (state, { payload: auth }) => ({
...state,
authError: null,
auth,
}),
[REGISTER_FAILURE]: (state, { payload: error }) => ({
...state,
authError: error,
}),
},
initialState,
);
export default auth;
import { combineReducers } from "redux";
import { all } from 'redux-saga/effects';
import auth, { authSaga } from './auth';
import loading from './loading';
const rootReducer = combineReducers(
{
loading,
auth,
},
);
export function* rootSaga() {
yield all([authSaga()]);
}
export default rootReducer;
./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import createSagaMiddleware from '@redux-saga/core';
import rootReducer, { rootSaga } from './modules';
import { BrowserRouter } from 'react-router-dom';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(sagaMiddleware))
);
sagaMiddleware.run(rootSaga);
ReactDOM.render(
<Provider store={ store }>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
reportWebVitals();
리덕스 관련 코드들을 작성했으므로 Form 컴포넌트에서 회원가입과 로그인을 진행할 수 있는 코드를 작성하겠습니다.
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { changeField, initializeForm, register } from '../../modules/auth';
import AuthForm from './AuthForm';
import { withRouter } from 'react-router-dom';
const RegisterForm = ({ history }) => {
const dispatch = useDispatch();
const {
form,
auth,
authError
} = useSelector(({
auth,
}) => ({
form: auth.register,
auth: auth.auth,
authError: auth.authError,
}));
...
// Handler that registers form
const onSubmit = e => {
e.preventDefault();
const {
email,
password,
passwordConfirm,
nickname,
phoneNumber,
} = form;
if(password !== passwordConfirm) {
// setError
return;
}
if([
email,
password,
passwordConfirm,
nickname,
phoneNumber,
].includes('')) {
// setError
return;
}
dispatch(register({
email,
password,
nickname,
phoneNumber,
}));
history.push('/');
};
...
};
export default withRouter(RegisterForm);
discovery-service, apigateway-service, config-server, auth-service를 구동시키고 테스트 결과를 보도록 하겠습니다.
회원가입이 잘 된 모습을 볼 수 있습니다.
다음 포스트에서는 로그인 관련 redux를 작성해보도록 하겠습니다.