2. FrontEnd/React / / 2024. 6. 11. 16:49

[React] AuthContext를 사용하여 로그인 관리하기

프로젝트에 로그인 기능을 넣던 도중에, 매번 페이지마다 useEffect로 로그인 관리하기엔 반복되는 코드가 많고 귀찮아서 chat GPT랑 어떻게 하는게 더 효율적인지 이야기하다가 AuthContext를 사용하여 하는 방법으로 리팩토링 하였다

 

  • 로그인 관련 로직 고민 ⇒ AuthContext를 사용해서 로그인하는걸로 리팩토링
    • useEffect를 사용하는 방법:
      • 장점:
        • 간단하고 직관적인 방법
        • 컴포넌트가 마운트될 때, 혹은 로그인 상태가 변경될 때마다 감지하여 적절한 동작을 수행
      • 단점:
        • 여러 컴포넌트에서 동일한 로직을 반복해서 사용
        • 컴포넌트 간의 의존성이 높아지고, 유지보수가 어려움
    • AuthContext를 사용하는 방법:
      • 장점:
        • 전역적으로 인증 정보를 관리할 수 있으므로 여러 컴포넌트에서 쉽게 접근
        • 코드 중복을 줄이고, 유지보수성을 향상
      • 단점:
        • 복잡한 구조를 가질 수 있으며, 처음에는 설정이 조금 복잡
        • 컴포넌트의 리렌더링이 필요할 때마다 인증 상태를 확인하여야 하므로 약간의 오버헤드가 발생
    ⇒ 프로젝트의 규모와 요구사항에 따라 적절한 방법을 선택 작은 규모의 프로젝트라면 useEffect를 사용하여 간단하게 처리 하지만 프로젝트 규모가 커지거나 인증 관련 로직이 복잡해진다면 AuthContext를 사용하여 전역적으로 관리하는 것이 유리

AuthContext 적용 방법

1️⃣ AuthContext.tsx 생성 (src > contexts폴더 안에 생성)

먼저, AuthContext를 생성합니다. createContext 함수를 사용하여 createContext를 생성합니다.

그런 다음, AuthProvider를 구현하여 인증 상태 및 로그인 및 로그아웃 함수를 제공합니다. useState를 사용하여 인증 상태를 관리합니다.

useAuth 훅을 사용하여 컴포넌트에서 AuthContext에 액세스할 수 있도록 합니다.

import React, { createContext, useContext, useState } from 'react';
import { loginAPI, meAPI } from '../apis/auth/authAPI';
import { removeTokensFromCookies, setTokensInCookies } from '../lib/utils/setTokensInCookies';
import useUserStore from '../apis/user/useUserStore';
import { TAuthContextType, TAuthProviderProps } from '../types/auth';

const AuthContext = createContext<TAuthContextType | undefined>(undefined);

export default function AuthProvider({ children }: TAuthProviderProps) {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const { setUser } = useUserStore();

  const login = async (username: string, password: string): Promise<void> => {
    try {
      const res = await loginAPI({ username, password });
      const accessToken = res.data.accessToken;
      const refreshToken = res.data.refreshToken;

      // accessToken, refreshToken을 Cookies에 저장
      setTokensInCookies({ accessToken, refreshToken });

      // API호출하여 유저 정보 가져오기
      const userResponse = await meAPI();
      const userData = userResponse.data;
      console.log('meAPI userResponse - userData : ', userData);

      // zustand에 사용자 데이터 저장
      if (userData) setUser(userData);
      setIsLoggedIn(true);
      //  return userData;
    } catch (error) {
      // 로그인 오류날 경우, 에러모달을 띄움
      console.error('loginAPI - error: ', error);
      throw error;
    }
  };

  const logout = () => {
    setIsLoggedIn(false);
    // 로그아웃 로직
    // 쿠키에 저장된 accessToken과 refreshToken 삭제
    removeTokensFromCookies();
  };

  return <AuthContext.Provider value={{ isLoggedIn, login, logout }}>{children}</AuthContext.Provider>;
}

export const useAuth = (): TAuthContextType => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};
export interface TAuthContextType {
  isLoggedIn: boolean;
  login: (username: string, password: string) => Promise<void>;
  logout: () => void;
}

export interface TAuthProviderProps {
  children: React.ReactNode;
}

 

2️⃣  index 혹은 App.tsx에 감싸기

App또는 index 컴포넌트에서 AuthProvider를 사용하여 앱 전역에서 인증 상태를 관리합니다.

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
import reportWebVitals from './reportWebVitals';
import { CookiesProvider } from 'react-cookie';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ConfigProvider } from 'antd';
import AuthProvider from './contexts/AuthContext';

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

const queryClient = new QueryClient();

root.render(
  <React.StrictMode>
    <AuthProvider>
      <QueryClientProvider client={queryClient}>
        <BrowserRouter>
          <CookiesProvider>
            <ConfigProvider
              theme={{
                components: {
                  Tabs: {
                    /* 상단 탭 커스텀 하는 방법 */
                    itemSelectedColor: 'white', // 상단탭 : 선택했을때 폰트 색상
                    itemColor: 'black', // 상단탭 : 기본적 폰트 색상
                  },
                },
              }}
            >
              <App />
            </ConfigProvider>
          </CookiesProvider>
        </BrowserRouter>
      </QueryClientProvider>
    </AuthProvider>
  </React.StrictMode>
);
reportWebVitals();

 

AuthContext를 사용하여 앱에서 인증 정보를 전역적으로 관리할 수 있습니다.

3️⃣ 사용 예시

  const onFinished = async (data: TLoginData) => {
    const username = data.email;
    const password = data.password;

    // "이메일 기억하기"가 체크된 경우, 입력한 이메일을 쿠키에 저장 (2,000초 = 약 33분)
    if (isRemember)
      setCookie('rememberUserEmail', username, { maxAge: 2000000 });
    // 입력된 사용자 이름과 비밀번호를 사용하여 로그인함수를 호출
    if (username && password) {
      try {
        await login(username, password);
      } catch (error) {
        console.error('로그인 실패: ', error);
        // 로그인 오류날 경우, 에러모달을 띄움
        setPostState(true);
      }
    }
  };

위와같이 login함수를 호출하여 사용하는 예시이고

import React from 'react';
import { Route, Routes, Navigate } from 'react-router-dom';
import CommonLayout from './components/Layouts/CommonLayout';
import AuthLayout from './components/Layouts/AuthLayout';
import { LOGIN_ROUTES, ROUTES } from './constants/url';
import { useAuth } from './contexts/AuthContext';

export default function App() {
  // 로그인 후 로그인페이지 혹은 메인으로 이동하는 로직
  const { isLoggedIn } = useAuth();

  return (
    <div className='App'>
      <Routes>
        {/* 왼쪽 메뉴들이 있는 컴포넌트들 */}
        <Route element={isLoggedIn ? <CommonLayout /> : <Navigate to='/login' replace />}>
          {Object.values(ROUTES).map((route) => (
            <Route key={route.path} path={route.path} element={route.element} />
          ))}
        </Route>
        {/* 로그인하는 페이지로, 왼쪽 메뉴가 없는 AuthLayout */}
        <Route element={!isLoggedIn ? <AuthLayout /> : <Navigate to='/' replace />}>
          {Object.values(LOGIN_ROUTES).map((route) => (
            <Route key={route.path} path={route.path} element={route.element} />
          ))}
        </Route>
      </Routes>
    </div>
  );
}

위는 로그인이 되었는지 알수있는 isLoggedIn을 가져와서 확인하고 안되었으면 로그인페이지로 아니면 메인페이지로 이동하는 로직입니다

 

이렇게 되어 useEffect를 매 페이지마다 작성하기보다 Route에서 감지하도록 할수있습니다

728x90
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유