전체 Blog 코드는 Gihub에 올려두었다.
Github: https://github.com/OseungKwon/practice-react/tree/main/blog/front2
initiallizeForm
디스패치
(초기 상태를 만드는 과정)
dispatch(initializeForm('register'));
Redux [initiallizeForm]
redux는 다음과 같은 디스패치 명령을 받는다.
const init = { register: { username: '', password: '', passwordConfirm: '', }, login: { username: '', password: '', }, auth: null, authError: null, };
이후, 기존 init의 형식에서 regster 부분만 가져와 다음과 같이 설정한다.
register:{ username: '', password: '', passwordConfirm: '' }
input 태그에 글씨를 쓰면, onChange 이벤트가 발생한다.
이를 통해서 글씨 입력시마다 value값을 수정해준다.
const onChange = (e) => {
const { value, name } = e.target;
dispatch(
changeField({
form: 'register',
key: name,
value,
}),
);
};
Redux [changeField]
form, key, value 값을 payload로 받아서 액션에 전달해준다.
[CHANGE_FIELD]: (state, { payload: { form, key, value } }) => produce(state, (draft) => { draft[form][key] = value; }),
immer의 produce를 사용해 ...state를 다른 방법을 사용해 주었으며,
draft[from][key]가 의미하는 것은
만약, form: 'register', key: 'password', value: '1234'라면register:{ password: '1234'
가 들어가게 된다.
이제 input을 모두 완료하고 회원가입
버튼이 눌리면 어떤 동작을 하는지 알아보자.
onSubmit 이벤트가 실행되면
dispatch(register({username, password}))
REDUX
auth/REGISTER
loading/START_LOADING
auth/REGISTER_SUCCESS
registerSaga
registerSaga를 통해 받은payload: response.data
는
auth/REGISTER_SUCCESS에서의payload: auth
가 된다.
Loading/FINISH_LOADING
액션에 따른 흐름을 간단하게 나타내 보았는데, 설명을 덧붙이자면,
register 액션을 받으면,
registerSaga는 startLoading을 하며, 정상적으로 실행된 경우, authAPI.register에 POST 작업을 거친다.
payload로 response.data를 받아온다.
3번의 reponse.data는 REGISTER_SUCCESS에서 받는 payload: auth와 같은 값이다.
여기까지 과정을 거치면 다시 registerForm으로 돌아온다.
전체 코드:
// 회원 인증 Form Redux
import { createAction, handleActions } from 'redux-actions';
// immer
import produce from 'immer';
// redux-saga 관련
import createRequestSaga, {
createRequestActionTypes,
} from '../lib/createRequestSaga';
import { takeLatest } from 'redux-saga/effects';
// 모든 rest api 가져오기
import * as authAPI from '../lib/api/auth';
// 회원인증 onChange, onSubmit 관련
const CHANGE_FIELD = 'auth/CHANGE_FIELD';
const INITIALIZE_FORM = 'auth/INITIALIZE_FORM';
// 회원가입 관련 saga
// 비구조화 할당 통해서 createRequestActionTyle에서의
// [type, SUCCESS, FAILURE]을 [REGISTER, REGISTER_SUCCESS, REGISTER_FAILURE]로
const [REGISTER, REGISTER_SUCCESS, REGISTER_FAILURE] =
createRequestActionTypes('auth/REGISTER');
// 로그인 관련 saga
const [LOGIN, LOGIN_SUCCESS, LOGIN_FAILURE] =
createRequestActionTypes('auth/LOGIN');
// onChange
export const changeField = createAction(
CHANGE_FIELD,
({ form, key, value }) => ({
form,
key,
value,
}),
);
// Form 초기 상태
export const initializeForm = createAction(INITIALIZE_FORM, (form) => form);
// 회원가입
export const register = createAction(REGISTER, ({ username, password }) => ({
username,
password,
}));
// 로그인
export const login = createAction(LOGIN, ({ username, password }) => ({
username,
password,
}));
// [회원가입 사가]
// register === client.post('/api/auth/register', { username, password });
const registerSaga = createRequestSaga(REGISTER, authAPI.register);
// [로그인 사가]
// login === client.post('/api/auth/login', { username, password });
const loginSaga = createRequestSaga(LOGIN, authAPI.login);
// redux-saga는 "제너레이터 함수"를 사용해 비동기 작업을 관리
export function* authSaga() {
// 기존에 진행중이던 작업이 있다면 취소,
// 가장 마지막으로 실행된 작업만 수행
yield takeLatest(REGISTER, registerSaga);
yield takeLatest(LOGIN, loginSaga);
}
// 초기화
const init = {
register: {
username: '',
password: '',
passwordConfirm: '',
},
login: {
username: '',
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]: init[form],
}),
[REGISTER_SUCCESS]: (state, { payload: auth }) => ({
...state,
authError: null,
auth,
}),
[REGISTER_FAILURE]: (state, { payload: error }) => ({
...state,
authError: error,
}),
[LOGIN_SUCCESS]: (state, { payload: auth }) => ({
...state,
authError: null,
auth,
}),
[LOGIN_FAILURE]: (state, { payload: error }) => ({
...state,
authError: error,
}),
},
init, // state에 딱히 값이 없으면 init이 대신 들어감
);
export default auth;
if(auth)
가 참인 경우, dispatch(check())
를 한다.
redux [CHECK]
loading/START_LOADING
user/CHECK_SUCCESS
registerSaga
registerSaga를 통해 받은payload: response.data
는
ser/CHECK_SUCCESS에서의payload: user
가 된다.
Loading/FINISH_LOADING
전체 코드
// 사용자 상태
import { createAction, handleActions } from 'redux-actions';
import { takeLatest, call } from 'redux-saga/effects';
import * as authAPI from '../lib/api/auth';
import createRequestSaga, {
createRequestActionTypes,
} from '../lib/createRequestSaga';
// 새로고침 이후 로그인 처리
const TEMP_SET_USER = 'user/TEMP_SET_USER';
// 회원 정보 확인
const [CHECK, CHECK_SUCCESS, CHEKC_FAILURE] =
createRequestActionTypes('user/CHECK');
// 로그아웃
const LOGOUT = 'user/LOGOUT';
export const tempSetUser = createAction(TEMP_SET_USER, (user) => user);
export const check = createAction(CHECK);
export const logout = createAction(LOGOUT);
// check === client.get('/api/auth/check');
// 서버에서 check에 저장된 정보를 가져온다.
const checkSaga = createRequestSaga(CHECK, authAPI.check);
// + 응답이 중요하기에 따로 createRequestSaga를 만들어 작업을 처리해 주었다.
// 로컬 스토리지에서 오류 발생시,
function checkFailureSaga() {
try {
localStorage.removeItem('user');
} catch (e) {
console.log('localStorage is not working');
}
}
// 로그아웃 사가
function* logoutSaga() {
try {
// call 을 사용하면 특정 함수를 호출하고,
// 결과물이 반환 될 때까지 기다려줄 수 있다.
yield call(authAPI.logout);
localStorage.removeItem('user');
} catch (e) {
console.log(e);
}
}
// user에서 사용되는 사가들은 모두 여기에 넣어져 사용된다.
export function* userSaga() {
yield takeLatest(CHECK, checkSaga);
yield takeLatest(CHEKC_FAILURE, checkFailureSaga);
yield takeLatest(LOGOUT, logoutSaga);
}
const init = {
user: null,
checkError: null,
};
export default handleActions(
{
[TEMP_SET_USER]: (state, { payload: user }) => ({
...state,
user,
}),
// 회원 정보가 존재하면,
[CHECK_SUCCESS]: (state, { payload: user }) => ({
...state,
user,
checkError: null,
}),
// 회원 정보가 존재하지 않으면 에러 반환
[CHEKC_FAILURE]: (state, { payload: error }) => ({
...state,
user: null,
checkError: error,
}),
[LOGOUT]: (state) => ({ ...state, user: null }),
},
init,
);
왼쪽은 RegisterFrom,
오른쪽은 Redux 부분이다.
dispatch를 통해 RegisterForm과 Redux가 상호작용하는 과정을 볼 수 있다.