[React & Redux] Redux를 이용해 로그인 구현하기

Hyuk·2023년 4월 10일
1

개요

Redux는 리액트에서 가장 많이 사용되고 있는 상태관리 라이브러리이다.
Redux를 사용하게 되면 상태를 글로벌하게 관리할 수 있고 효율적으로 관리할 수가 있다.
이를 이용해서 간단하게 로그인을 구현하는 포스트를 작성하고자 한다.

위의 이미지와 같이 사용자가 로그인을 하게 되면 Header부분에 로그인이 아닌 마이페이지 버튼이 보이게 된다.
이를 위해선 사용자가 로그인을 했는지 하지 않았는지 판단할 수 있는 데이터가 필요하고, 해당 데이터는 Redux를 통해 관리하게 된다.

Redux 준비

Redex를 준비하기에 앞서 데이터를 영구적으로 보관하기 위한 모듈이 필요하다.
사용자가 새로고침을 하게되면 스토어에 있는 정보는 사라지기 때문에 이를 방지하기 위해 redux-persist 이용한다.

redux-persist

redux-persist 는 데이터를 local storagesession storage에 저장하는 모듈이다.

session storage는 페이지를 새로고침해도 데이터가 남아 있고, local storage는 브라우저를 껐다 켜도 데이터가 남아 있다.

다음과 같이 local storage에 데이터를 저장할 수 있다.

import storage from 'redux-persist/lib/storage';

Redux로 데이터 관리

이제 상태를 관리할 로직을 작성해보자.
먼저, 리액트 프로젝트 내에 module 이라는 디렉토리를 생성해 준 뒤, index.jsuser.js 를 생성해보자.

user.js 에는 로그인에 관련된 액션 타입과 리듀서를 작성할 예정이고, index.js 에는 store를 생성하는 역할을 할 예정이다.

리듀서(Reducer)

로그인에 관련한 리듀서는 다음과 같이 코드를 작성할 수가 있다.

// src/module/user.js

// Redux에서 관리할 초기 상태
const initialState = {
    user: {},
}

// 액션 타입
const SET_USER = 'SET_USER';

// 액션 함수
export const getToken = data => ({ type: SET_USER, data });

// 리듀서
export default function user(state = initialState, action) {
    switch(action.type) {
        case SET_USER:
            return {
                ...state,
                user: action.data
            }
        default:
            return state;
    }
}

스토어(store)

리듀서와 redux-persistcombine 해서 store를 생성하는 코드는 다음과 같이 작성할 수 있다.

import { combineReducers } from 'redux'
import { persistReducer } from 'redux-persist';
import user from './user'
import storage from 'redux-persist/lib/storage';

const persistConfig = {
    key: "root",
    storage,   // local storage에 저장.
    whitelist: ["user"],
}

const rootReducer = combineReducers({ user });

export default persistReducer(persistConfig, rootReducer)

리액트 프로젝트에 적용

위에서 리듀서와 스토어의 코드를 작성했다면 리액트 프로젝트에 적용시켜주면 된다.

// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './modules';
import { persistStore } from 'redux-persist';
import { PersistGate } from 'redux-persist/integration/react';

const root = ReactDOM.createRoot(document.getElementById('root'));

const store = createStore(rootReducer);

const persistor = persistStore(store);

root.render(
  <React.StrictMode>
    <Provider store={store}>
      <PersistGate persistor={persistor}>
        <App />
      </PersistGate>
    </Provider>
  </React.StrictMode>
);

export default store;
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

토큰 확인

이제 사용자가 로그인을 했을 때에 토큰 값을 Redux를 통해 관리할 수 있게 됐다.

이를 Redux에서 관리하는 상태값을 조회하여 확인하거나 API 요청 시에 확인하여 요청해야 한다.

useSelector

맨 위의 이미지를 보면 사용자가 로그인을 하면 로그인 UI가 아닌 마이페이지 UI가 나오는 것을 확인할 수가 있다.

Header 부분에서 사용자의 토큰 값을 글로벌로 확인하여 검증하는 것이다.
이처럼 Redux 를 통해 관리되는 상태값을 조회할 수 있는 함수가 useSelector이다.

다음과 같이 useSelector 를 사용할 수가 있다.

import { useSelector } from 'react-redux';

export const Nav => () => {
  const { user } = useSelector(state => state.user);	
  
  return(
    <>
    	...
    	<div>
    		<input type="button" value={user.token ? "마이페이지" : "로그인"} />
      	</div>
    	...
    </>
  )
}

Axios interceptors

Axios interceptors는 requestresponsethencatch에 의해 처리되기 전에 전역적으로 가로채게 허용해 주는 Axios 라이브러리이다.

예를 들어, 사용자가 어떤 액션을 취하는 API를 호출할 때 매번 로그인을 확인하는 로직을 작성한다면 코드의 길이가 엄청나게 길어짐과 동시에 유시보수에도 어려움을 줄 것이다.

필자는 이것을 Axios interceptors를 이용해 HTTP Authorizaition 요청 헤더에 JWT-Token을 보내서 해당 API에 요청하기 전에 검증시키는 로직을 작성할 것이다.

다음과 같이 코드를 작성할 수가 있다.

import axios from "axios";
import store from '../index'

const baseURL = '...'

const baseInstance = axios.create({
  baseURL,
  header: {
    Authorization: ''
  },
})

baseInstance.interceptors.request.use((config) => {
  const token = store.getState().user.user.token;
  try {
    if (token) {
      config.headers['Authorization'] = `JWT ${token}`
    }

    return config;
  } catch (error) {
    console.error(error)
  }
})

baseInstance.interceptors.response.use((data) => data)

export const apiRequest = {
  ...
}
profile
프론트엔드 개발자

0개의 댓글