Face Review 프로젝트는 유저의 로그인상태, 로그인한 유저의 정보에 따른 튜토리얼 페이지 로딩 여부 및 영상 추천 등 여러가지로 프론트에서도, 서버와의 통신에서도 필요로하는 유저의 정보를 브라우저에서 가지고 관리하기 위해 Zustand라는 상태관리 도구를 사용하였다.
React 프로젝트에서는 보통 Redux를 많이 사용하는 것으로 알고 있는데, 비교적 최신 기술인 Zustand가 더 깔끔한 코드로 간단하고 편리하게 관리할 수 있다고 생각해 Zustand를 사용하였다.
우선 TypeScript + React 프로젝트에서 Zustand를 사용하는 간단한 예시를 살펴보자.
// store.ts
import create from 'zustand';
interface AppState {
count: number;
increment: () => void;
decrement: () => void;
}
export const useAppStore = create<AppState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
AppState라는 interface를 통해 상태의 타입을 정의해주고, zustand의 create 함수를 통해 useAppStore라는 Store를 생성해준다.
count는 상태, increment와 decrement는 count라는 상태를 업데이트하는 함수이다.
이렇게 만들어진 Store는 아래와 같이 사용하여 관리할 수 있다.
// App.tsx
import React from 'react';
import { useAppStore } from './store';
const App: React.FC = () => {
// Zustand 스토어 사용
const { count, increment, decrement } = useAppStore();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
};
export default App;
Increment, Decrement 버튼을 클릭하면 useAppStore의 업데이트 함수가 호출되어 count의 값이 변경되고 그 값을 useAppStore에서 가지고 관리하게 된다.
이제 Face Review에서의 Zustand 사용을 살펴보자.
// AuthStore.ts 일부
import create from "zustand";
import { devtools, persist } from "zustand/middleware";
interface AuthState {
is_sign_in: boolean;
is_admin: boolean;
user_name: string;
user_favorite_genres: string[];
setUserInfo: ({ is_admin, is_sign_in, user_name, user_favorite_genres } : {
is_admin: boolean;
is_sign_in: boolean;
user_name: string;
user_favorite_genres: string[];
}) => void;
setUserName: ({ user_name }: { user_name: string }) => void;
setUserFavoriteGenres: ({ user_favorite_genres }: { user_favorite_genres: string[] }) => void;
}
export const useAuthStorage = create<AuthState>()(
devtools(
persist(
(set) => ({
// 초기 상태
is_admin: false,
is_sign_in: false,
user_name: "",
user_favorite_genres: [],
// 업데이트 함수
setUserInfo: ({ is_admin, is_sign_in, user_name, user_favorite_genres }) =>
set((state) => ({
is_admin: is_admin,
is_sign_in: true,
user_name,
user_favorite_genres,
})),
setUserName: ({ user_name }) =>
set((state) => ({
user_name,
})),
setUserFavoriteGenres: ({ user_favorite_genres }) =>
set((state) => ({
user_favorite_genres,
})),
}),
{
name: "auth-storage",
}
)
)
);
먼저 AuthState라는 interface로 상태의 타입을 정의해주고, create함수를 통해 useAuthStorage라는 store를 만들어준다.
위에서 살펴본 예시와 같이 업데이트 함수들을 만들어주고, 전달받은 함수의 인자들을 통해 store의 상태들을 업데이트 해준다.
여기서, devtools와 persist라는 미들웨어를 사용했는데,
먼저, devtools는 개발자 도구를 통해 Zustand의 상태와 액션(업데이트)를 모니터링할 수 있게 해주는 기능을 한다. (Chrome Extension의 Redux devtools 등을 사용하면 쉽게 확인할 수 있다.)
다음으로 persist는 로컬스토리지를 사용하여 Zustand store의 상태를 저장하고 복원할 수 있게 해주는 미들웨어이다. 여기서는 name: "auth-storage"에서 auth-storage라는 이름으로 로컬스토리지에 저장했는데, 이렇게 persist를 사용하면 페이지를 이동하거나 새로고침을 할 때에도 이전의 상태가 그대로 유지되어 사용자 경험을 개선할 수 있다.
아래는 Face Review에서 생성된 useAuthStorage의 활용이다.
// 유저정보 수정 페이지의 tsx코드 일부
const { user_name, setUserName, user_favorite_genres, setUserFavoriteGenres } = useAuthStorage();
const handleEditButtonClick = () => {
changeName({ new_name: nickName })
.then((res) => {
if (res.status === 200) {
setUserName({ user_name: nickName });
navigate("/my");
}
})
.catch((error) => {
console.log(error);
});
changeFavoriteGenre({
user_favorite_genre_1: selectedCategories[0],
user_favorite_genre_2: selectedCategories[1],
user_favorite_genre_3: selectedCategories[2],
})
.then((res) => {
if (res.status === 200) {
setUserFavoriteGenres({ user_favorite_genres: selectedCategories });
}
})
.catch((error) => {
console.log(error);
});
};
changeName()과 changeFavoriteGenre()는 유저의 이름 및 선호 장르를 변경하는 api 요청을 서버에 보내는 함수이다.
여기서는 setUserName()과 setUserFavoriteGenres()라는 useAuthStorage의 상태 업데이트 함수를 사용해서, 서버에 유저정보 변경을 요청이 정상적으로 반영되었을 때에 로컬스토리지에 저장되어 있는 각각의 상태도 함께 업데이트 해주는 것이다.
처음 사용해보는 Zustand였는데, 너무 편리해서 앞으로 개인 프로젝트를 한다면 또 쓸 것 같다.
프로젝트를 진행할 때는 머리가 터질 것 같았는데, 마치고나서 이렇게 정리하다 보니까 그때는 마음이 급해서 몰랐지만 나 자신이 많이 성장한 것 같아 뿌듯하고 정리하는 과정에서도 애매했던 개념들이 정리가 되어서 유익한 시간인 것 같다.👍🏻👍🏻