[React, Redux] Blog 회원인증 과정 이해하기

자몽·2021년 8월 5일
2

React

목록 보기
5/7

2편. 회원인증 과정 이해하기

전체 Blog 코드는 Gihub에 올려두었다.
Github: https://github.com/OseungKwon/practice-react/tree/main/blog/front2

1. auth/INITIALIZE_FORM

RegisterForm

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: ''
}

2. auth/CHANGE_FIELD

RegisterForm

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'

가 들어가게 된다.

3. auth/REGISTER

이제 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

액션에 따른 흐름을 간단하게 나타내 보았는데, 설명을 덧붙이자면,

  1. register 액션을 받으면,

  2. registerSaga는 startLoading을 하며, 정상적으로 실행된 경우, authAPI.register에 POST 작업을 거친다.

  3. payload로 response.data를 받아온다.

  4. 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;

4. user/CHECK

registerForm

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,
);

3,4 요약

왼쪽은 RegisterFrom,
오른쪽은 Redux 부분이다.

dispatch를 통해 RegisterForm과 Redux가 상호작용하는 과정을 볼 수 있다.

profile
꾸준하게 공부하기

0개의 댓글