관리 메뉴

거니의 velog

(9) 프론트엔드 프로젝트 : 시작 및 회원 인증 구현 9 본문

React_프론트엔드 프로젝트

(9) 프론트엔드 프로젝트 : 시작 및 회원 인증 구현 9

Unlimited00 2024. 2. 23. 13:31

(3) 로그아웃 기능 구현

* 이번에는 로그아웃 기능을 구현해 보자. 로그아웃 기능을 구현하는 것은 간단하다. 로그아웃 API를 호출하고, localStorage 안의 값을 없애 주면 된다.

* 먼저 lib/api/auth.js 파일을 열어서 logout 함수를 만들어 주자.

import client from './client';

// 로그인
export const login = ({ username, password }) =>
  client.post('/api/auth/login', { username, password });

// 회원가입
export const register = ({ username, password }) =>
  client.post('/api/auth/register', { username, password });

// 로그인 상태 확인
export const check = () => client.get('/api/auth/check');

// 로그아웃
export const logout = () => client.post('/api/auth/logout');

* 이어서 LOGOUT 이라는 액션을 만들고, 이 액션이 디스패치되었을 때 API 호출 후 localStorage의 user 값을 지워준다. 추가로 리듀서에서는 스토어의 user 값을 null로 설정할 것이다.

* 로그아웃의 경우에는 성공/실패 여부가 중요하지 않으므로 LOGOUT_SUCCESS, LOGOUT_FAILURE 와 같은 액션을 따로 만들지 않겠다.

[modules/user.js]

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

const checkSaga = createRequestSaga(CHECK, authAPI.check);

function checkFailureSaga() {
  try {
    localStorage.removeItem('user'); // localStorage 에서 user 제거
  } catch (e) {
    console.log('localStorage is not working');
  }
}

function* logoutSaga() {
  try {
    yield call(authAPI.logout); // logout API 호출
    localStorage.removeItem('user'); // localStorage 에서 user 제거
  } catch (e) {
    console.log(e);
  }
}

export function* userSaga() {
  yield takeLatest(CHECK, checkSaga);
  yield takeLatest(CHECK_FAILURE, checkFailureSaga);
  yield takeLatest(LOGOUT, logoutSaga);
}

const initialState = {
  user: null,
  checkError: null,
};

export default handleActions(
  {
    [TEMP_SET_USER]: (state, { payload: user }) => ({
      ...state,
      user,
    }),
    [CHECK_SUCCESS]: (state, { payload: user }) => ({
      ...state,
      user,
      checkError: null,
    }),
    [CHECK_FAILURE]: (state, { payload: error }) => ({
      ...state,
      user: null,
      checkError: error,
    }),
    [LOGOUT]: (state) => ({
      ...state,
      user: null,
    }),
  },
  initialState,
);

* 다 작성한 뒤에는 logout 액션 생성 함수를 디스패치하는 onLogout 함수를 만들어서 Header 컴포넌트에 전달해 주자.

[containers/common/HeaderContainer.js]

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import Header from '../../components/common/Header';
import { logout } from '../../modules/user';

const HeaderContainer = () => {
  const { user } = useSelector(({ user }) => ({ user: user.user }));
  const dispatch = useDispatch();
  const onLogout = () => {
    dispatch(logout());
  };
  return <Header user={user} onLogout={onLogout} />;
};

export default HeaderContainer;

* 그리고 Header 컴포넌트에서 로그아웃 버튼을 누르면 해당 함수가 호출되도록 구현하자.

[components/common/Header.js]

(...)

const Header = ({ user, onLogout }) => {
  return (
    <>
      <HeaderBlock>
        <Wrapper>
          <Link to="/" className="logo">
            REACTERS
          </Link>
          {user ? (
            <div className="right">
              <UserInfo>{user.username}</UserInfo>
              <Button onClick={onLogout}>로그아웃</Button>
            </div>
          ) : (
            <div className="right">
              <Button to="/login">로그인</Button>
            </div>
          )}
        </Wrapper>
      </HeaderBlock>
      <Spacer />
    </>
  );
};

export default Header;

* 이제 브라우저에서 다시 로그인한 다음, 로그아웃 버튼을 눌러 보자. 로그아웃이 잘 진행되었는가?

로그아웃


4. 정리

* 회원 인증을 위한 기능이 모두 구현되었다. 회원 인증 기능을 구현하는 과정에서, 우리는 UI를 구성하고 애플리케이션에 필요한 상태 관리도 해 주었다. API를 연동하는 과정에서 리덕스와 redux-saga도 사용되었다. 추가로 이 프로젝트에서는 클래스형 컴포넌트가 단 한번도 사용되지 않았으며, 앞으로도 사용되지 않을 예정이다.

* 앞으로 해야 할 작업도 결국 비슷한 작업이다. 다음 장에서는 글쓰기 기능을 구현해 볼 것이다.