아이디와 비밀번호를 받아 서버에서 사용자 검증을 한 후 토큰을 발급받는 기능은 완성을 했다.
이제 프론트에서 어디에 저장할 지, 어떻게 로그인 상태를 관리할 지 고민을 해야 했다.
발급받은 토큰을 프론트에 저장하는 방법으로는 로컬 스토리지와 쿠키 중에서 고민을 하다가 로컬 스토리지에 저장을 했다.
하지만 생각을 해보면 쿠키는 데이터가 영구저장되지 않기 때문에 만료일을 설정하거나, 설정하지 않는다면 세션처럼 브라우저가 종료되면 데이터가 지워진다. 반면 로컬스토리지는 직접 지우지 않는 한 영구적으로 저장되므로, 로그아웃 버튼을 누르지 않고 그냥 브라우저를 종료시킨다면 계속해서 로그인 상태가 남아있지 않을까 하는 생각이 들었다.
그래서 먼저 프로젝트를 완성을 시킨 후, 리팩토링하는 시점에서 토큰을 쿠키에 저장하는 방식으로 개선을 하면서 다른 방법 또한 고민을 해 볼 생각이다.
다음으로는 로그인 상태관리인데, 이전 프로젝트에서는 사실 로그인 상태를 전역적으로 관리하지 않았다.
팀 프로젝트를 할 때는 전체적인 프로젝트에서 구조를 보는 거시적인 시선이 부족했었고, 내가 담당한 페이지에 있어서 필요할 때마다 토큰이 있는지 없는지에 대한 체크를 통해 로그인 상태를 관리했었다.
하지만 1인 프로젝트를 하면서 전체적인 컴포넌트를 모두 작성하면서 생각해 보니 전역적인 로그인 상태 관리의 필요성을 느끼게 되었다.
이러한 메인 페이지에서, 오른쪽의 프로필 박스와 왼쪽의 컨텐츠 박스 두 개의 컴포넌트는 각각 따로 동작하는데, 컨텐츠 박스에서 회원 로그인을 했을 경우에만 렌더링 되는 컨텐츠들이 있다.
이전같았으면 프로필 박스와 컨텐츠 박스 각각의 컴포넌트에서 토큰이 있는지 없는지 로컬 스토리지를 확인하고, 그에 맞는 조건문을 작성했었을 것이다. 결과적으로 나타나는 화면은 동일하겠지만 뭔가 이러한 컴포넌트들이 많아지면 많아질수록 그때그때 각자의 컴포넌트에서 일일이 확인을 하고 각자 리렌더링 시키는 것은 비효율적일 것 같았다.
그래서 전역적으로 상태를 관리할 수 있는 방법을 찾아봤는데,
보통은 Context와 Redux를 사용한다고 한다.
Context는 react에서 기본으로 제공하는 훅이고, Redux는 외부 모듈이기 때문에 따로 설치가 필요했다.
또한, Context는 작은 규모의 프로젝트나, 특정 상태변수의 조회와 같은 단순한 상태관리에 사용하고
Redux는 많은 곳에서 전역변수를 필요로 하거나, 상태의 변화가 잦고 복잡한 대규모의 프로젝트에서 주로 사용한다고 한다.
전공자들 프로젝트 같은 경우에선 로그인 상태를 필요로 하는 곳이 그렇게 많지도 않고, 전역적으로 관리해야 할 상태가 많거나 복잡한 프로젝트가 아니기 때문에 Context로도 충분하다고 판단했다.
//LogInContext.js
import { createContext, useState, useContext } from "react";
const LogInContext = createContext();
export const LogInProvider = ({children}) =>{
const [isLogIn, setIsLogIn] = useState(false)
const setLogIn = ()=>{
setIsLogIn(true)
}
const setLogOut=()=>{
setIsLogIn(false)
}
return (
<LogInContext.Provider value={{isLogIn, setLogIn, setLogOut}}>
{children}
</LogInContext.Provider>
)
}
export const useLoginContext =()=>{
const logInContext = useContext(LogInContext)
if(!logInContext){
throw new Error('useLogin은 반드시 LogInProvider내에서 사용되어야 합니다.')
}
return logInContext
}
이렇게 로그인 상태를 나타내는 변수 isLogIn를 useState를 통해 false를 기본값으로 설정하고, 그 상태를 변경시킬 setLogIn, setLogOut 함수를 다른 컴포넌트에서 사용할 수 있도록 Provider의 value값으로 설정해주었다.
그리고 최상위 컴포넌트인 App.js에 가서 LogInProvider로 감싸면 준비는 완료되었다
function App() {
return (
<LogInProvider>
<Routes>
<Route path="/*" element={<MainPage />} />
<Route exact path="/login" element={<LogIn />} />
<Route exact path="/find" element={<Find />} />
<Route exact path="/join" element={<Join />} />
<Route exact path="/error" element={<Error />} />
</Routes>
<ScrollToTop />
</LogInProvider>
);
}
전역 상태를 사용할 로그인, 로그아웃 버튼으로 가서 만들어둔 useLogInContext의 각 변수들을 선언해주고, 버튼을 눌렀을 때 역할에 맞는 로직을 작성했다.
//Login.js
const LogIn = () => {
const navigate = useNavigate();
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const {setLogIn} = useLoginContext();
const tryLogIn = (e) => {
e.preventDefault()
const emailPattern = /^[가-힣a-zA-Z0-9.-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (emailPattern.test(email)) {
axiosURL.post('/member/login',{
email:email,
password:password,
})
.then(res=>{
const token = res.data
localStorage.setItem("accessToken" ,token) // 토큰을 로컬스토리지에 저장
setLogIn() // 로그인 상태로 만들기
navigate('/');
}).catch(err=>{
e.preventDefault()
console.log(err)
alert("회원 정보가 일치하지 않습니다.")
})
} else {
alert('올바른 이메일 형식을 입력해주세요')
}
}
//이하생략
//프로필 박스에서의 logout버튼을 눌렀을 때
const {setLogOut} = useLoginContext();
const logout=()=>{
localStorage.removeItem("accessToken") //로컬스토리지에서 토큰 제거
setLogOut() //로그아웃 처리
}
그리고 각 컴포넌트에서는 isLogIn 변수를 통해서 각 상태에 맞는 컴포넌트를 렌더링하도록 했다.
결과적으로 로그인을 했을 때 로그인 상태변화함에 따라 관련된 컴포넌트들이 한꺼번에 리렌더링되고,
로그아웃 버튼을 눌렀을 때에도 마찬가지로 한꺼번에 모두 로그아웃상태로 바뀐 것을 확인했다.
하지만 큰 문제가 생겼는데, 바로 새로고침을 누르면 로그인 상태가 다시 초기화가 된다는 것이다.
알고보니, Context를 사용하면 새로고침을 할 때 다시 렌더링이 되면서 설정한 상태의 초기값으로 바뀌는 것이었다...
export const LogInProvider = ({children}) =>{
const [isLogIn, setIsLogIn] = useState(false)
//이하생략
}
처음 작성했었던 LogInProvider에서 isLogIn변수의 초기값으로 false, 즉 미로그인 상태를 지정해뒀기 때문에 새로고침을 하면 모두 로그아웃 처리가 되는 것이었는데,
이걸 해결하기 위한 방법으로 Redux를 사용하는 것이 있었다. Redux는 Context와 달리 새로고침을 해도 전역적인 상태가 변하지 않는데, 나는 이미 완성된 코드를 처음부터 다시 작성하기 보다 최대한 기존 코드를 살리면서 효율적으로 수정을 하고 싶었다.
그래서 결국 한가지 해결책을 찾았는데
새로고침이 될 때마다 초기값으로 돌아가는게 문제라면 그 초기값을 현재 상태로 설정하면 되지 않을까 했다.
export const LogInProvider = ({children}) =>{
const token = localStorage.getItem('accessToken')
const [isLogIn, setIsLogIn] = useState(token?true:false)
}
로컬스토리지에 발급받은 토큰이 있는지를 확인 후 로그인 삼항연산자를 통해 초기값을 설정해주어서
새로고침 시에도 로그아웃이 되지 않도록 할 수 있었다.
'프로젝트 > ✍️ [전공자들]' 카테고리의 다른 글
[전공자들 13] 좋아요/싫어요 구현하기 (mongodb/react/spring) (1) | 2023.12.17 |
---|---|
[전공자들 12] 댓글/답글 기능 만들기 (spring/mongodb 내장객체) (0) | 2023.12.16 |
[전공자들 10] JWT 이해하기(로그인, 토큰발급, 요청보내기) (1) | 2023.12.07 |
[전공자들 9] 회원가입 기능구현 (이메일 인증, 중복체크) (1) | 2023.12.05 |
[전공자들 8] 7일차 - 로그인 /회원가입/비밀번호 재설정 화면 (1) | 2023.11.30 |