회원가입/로그인 그리고 JWT인증 등 이번 프로젝트에서 생각보다 큰 비중을 차지했던 부분이다.
인증 및 인가, 권한 관리 등은 웹 개발의 기본이 되는 기술이니 이번 프로젝트를 통해 꼭 익히고 싶었다.
과제 필수사항에는 회원가입과 로그인 처리만을 요구하고 있었기에 새로고침 시 로그인이 풀리는 구조까지 구현을 하면 되었다.
로그인 및 회원가입을 완료 후 다른 페이지를 구현하는데 로그인이 풀리니 정보값을 불러오는데 귀찮음이 생기기 시작했다.
결국 도전 기능 가이드에 있는 로그인 유지 기능 구현을 완료 후 다음 단계로 넘어가기로 마음을 먹었었다.
가이드에는 localStorage나 sessionStorage를 활용해 JWT 토큰을 저장하고 페이지 로드 시 해당 코튼을 이요해 사용자의 로그인 상태를 복원하는 방법을 사용 할 것을 제시해주었다. 나는 이번 심화주차때 배웠던 Zustand를 통해 상태관리를 해보려 한다.
상태관리의 중요성
상태관리는 모든 리액트 프로젝트에서 가장 중요하다.
여기서 상태란 데이터와 UI의 현재 상태를 나타내주고, 상태가 변경되면 UI도 같이 변경(업데이트)가 되어야 하는 것을 기억하자!
보통 상태관리 라이브러리로 Redux를 많이들 생각한다. 저번 숙련주차때 Redux를 배우긴 했지만... 생각보다 너무 어려웠던 기억이 있다.
그래서 요즘에는 작고 빠르고 가벼운 Zustand를 많이들 활용한다고 한다.
그래서 이번 프로젝트에서 진행할때 사용했던 Zustand를 정리해보려 한다.
Zustand 설치
yarn add zustand
폴더와 파일 만들기
// src > zustand > useUserStore.js
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
const useUserStore = create(
persist(
(set) => ({
user: {
accessToken: '',
avatar: null,
nickname: '',
success: false,
userId: ''
},
loginUser: (data) =>
set(() => ({
user: {
accessToken: data.accessToken,
avatar: data.avatar,
nickname: data.nickname,
success: data.success,
userId: data.userId
}
})),
logoutUser: () =>
set(() => ({
user: {
accessToken: '',
avatar: null,
nickname: '',
success: false,
userId: ''
}
})),
changeNickname: (data) =>
set((state) => ({
user: {
...state.user,
nickname: data.nickname
}
}))
}),
{
name: 'user'
}
)
);
export default useUserStore;
persis 미들웨어
로컬 스토리지에 저장하여 페이지를 새로 고치거나 앱을 재시작해도 상태가 유지되도록 해줌
여기서 설정한 `이름`으로 로컬 스토리지에 데이터를 저장함
persist(
(set) => ({
// 상태와 업데이트 함수를 정의하는 부분
}),
{
name: 'user' // 로컬 스토리지에 저장될 키 이름
}
)
상태관리, 함수
정보가 저장되는 객체의 초기값을 지정
user: {
accessToken: '',
avatar: null,
nickname: '',
success: false,
userId: ''
}
로그인할때 전달되는 데이터를 이용해 user 상태 업데이트하는 함수
loginUser: (data) =>
set(() => ({
user: {
accessToken: data.accessToken,
avatar: data.avatar,
nickname: data.nickname,
success: data.success,
userId: data.userId
}
}))
여기서 `data`는 어디서 들어오는지 궁금할 수 있다.
이 data는 `API로부터 반환된 사용자의 정보 객체`이다. 이 객체는 zustand `상태를 업데이트`하는데 사용된다.
auth.js에서 로그인 API 호출의 응답을 받고 > 호출한 login.js에서 > zustand에 loginUser(data)로 넘겨 상태를 업데이트 하는 방식이다.
간단하게 데이터 흐름을 요약하자면
1. API호출 (Auth.js > login)
login(userData)를 호출하여 `API에서 사용자 로그인 정보를 확인`
2. 로그인 응답처리 (Login.jsx)
Login.jsx에서 로그인을 요청 후, 로그인 성공 시 `data가 넘어감`
3. 상태업데이트 (useUser.js > loginUser 함수)
zustand 스토어의 `user 상태를 업데이트`하여, API에서 받은 사용자 정보를 `전역상태`에 저장.
이것들을 간단하게 코드의 형태로 보자면
// auth.js
export const login = async (userData) => {
try {
const response = await axios.post(`${API_URL}/login`, userData);
return response.data; // 로그인 성공 시 데이터를 반환
} catch (error) {
console.log('error -> ', error);
return { success: false };
}
};
// login.jsx
const handleSubmit = async (e) => {
e.preventDefault();
const data = await login(userData);
if (data.success) {
alert('로그인 되었습니다.');
loginUser(data); // 성공 시, 상태 업데이트
navigate('/');
}
};
// useUserStore.js(zustand 관리)
const useUserStore = create(
persist(
(set) => ({
user: {
accessToken: '',
avatar: null,
nickname: '',
success: false,
userId: ''
},
loginUser: (data) =>
set(() => ({
user: {
accessToken: data.accessToken,
avatar: data.avatar,
nickname: data.nickname,
success: data.success,
userId: data.userId
}
})),
...
즉 `data`는 API에서 반환된 데이터로, 같은 데이터가 zustand 상태 관리와 연결되어 전체적으로 일관된 방식으로 사용되게 된다.
처음 프로젝트에서 사용해봤던 zustand.
프로젝트 중간부터 사용하다보니 조금은 더 어렵게 사용했던 것 같다. 프로젝트도 끝났고 곧 추석도 있으니 zustand에 대해 조금 더 공부하는 시간을 가지고 정리를 해봐야겠다.