
์ฅ๋ฐ๊ตฌ๋ ๋ชฉ๋ก ์ํ๋ ๋ค์ ๋ ๊ฐ์ง ์กฐ๊ฑด์ ์ถฉ์กฑํด์ผ ํ๋ค.
โก๏ธ ์ฆ, ์ฅ์ ํ์ ํ์ด์ง๋ฅผ ๋ฒ์ด๋ ๋์๋ง ์ฅ๋ฐ๊ตฌ๋ ๋ชฉ๋ก์ด ๋ฆฌ์ ๋์ด์ผ ํจ
์ฒซ ๋ฒ์งธ ์กฐ๊ฑด์ ์ถฉ์กฑ์ํค๊ธฐ ์ํด์ Zustand์ persist ๊ธฐ๋ฅ์ ์ฌ์ฉํ์๋ค.
persist๋?๋ธ๋ผ์ฐ์ ์ ์ํ(state)๋ฅผ ์ ์ฅํด์ ์๋ก๊ณ ์นจํด๋ ์ ์ง๋๋๋ก ๋์์ฃผ๋ ๊ธฐ๋ฅ
React ์ฑ์์๋ ์ํ๋ฅผ useState๋ Zustand ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๊ด๋ฆฌํจ โ ๊ทธ๋ฐ๋ฐ, ๋ธ๋ผ์ฐ์ ๋ฅผ ์๋ก๊ณ ์นจํ๋ฉด ์ด ์ํ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ ์ด๊ธฐํ๋จ
isLoggedIn: true์ธ ์ํ๋ฅผ ๋ง๋ค์๋๋ฐ, ์๋ก๊ณ ์นจํ๋ฉด ์ํ๋ ์ฌ๋ผ์ง๊ณ false๋ก ๋์๊ฐ๋ฒ๋ฆผpersist๋ฅผ ์ฐ๋ฉด, ์ํ๋ฅผ ๋ธ๋ผ์ฐ์ ์ localStorage๋ sessionStorage์ ์ ์ฅํด์ ์๋ก๊ณ ์นจํด๋ ๋ณต๊ตฌ๋๊ฒ ํด์คpersist ์ค์น ๋ฐฉ๋ฒpersist๋ zustand ํจํค์ง์ ๋ด์ฅ๋ ๋ฏธ๋ค์จ์ด์persist๋ฅผ ์ฌ์ฉํ ์ ์์npm install zustandpersist ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ (์์ )๋ก๊ทธ์ธ ์ํ๋ฅผ ์ ์ฅํ๊ธฐ
// store/useAuthStore.ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface AuthState {
isLoggedIn: boolean;
login: () => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
isLoggedIn: false,
login: () => set({ isLoggedIn: true }),
logout: () => set({ isLoggedIn: false }),
}),
{
name: 'auth-storage', // localStorage์ ์ ์ฅ๋ key ์ด๋ฆ
}
)
)
์ฌ์ฉ ์์ (์ปดํฌ๋ํธ)
import { useAuthStore } from './store/useAuthStore'
function App() {
const { isLoggedIn, login, logout } = useAuthStore();
return (
<div>
<h1>{isLoggedIn ? '๋ก๊ทธ์ธ๋จ' : '๋ก๊ทธ์์๋จ'}</h1>
<button onClick={login}>๋ก๊ทธ์ธ</button>
<button onClick={logout}>๋ก๊ทธ์์</button>
</div>
)
}
isLoggedIn์ true๋ก ๋ง๋ ๋ค, ๋ธ๋ผ์ฐ์ ๋ฅผ ์๋ก๊ณ ์นจํ๋ฉด ์ํ๊ฐ ์ ์ง๋จpersist์ ์ ์ฅ ์์น ์ง์ ํ๊ธฐ๊ธฐ๋ณธ์ ์ผ๋ก persist๋ ๋ก์ปฌ ์คํ ๋ฆฌ์ง(localStorage)์ ์ ์ฅํจ
์ ์ฅ ์์น๋ฅผ ์ธ์
์คํ ๋ฆฌ์ง(sessionStorage)๋ก ๋ฐ๊พธ๊ณ ์ถ๋ค๋ฉด storage ์ต์
์ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํด์ผ ํจ
persist(
(set) => ({ /* state */ }),
{
name: 'my-storage',
storage: createJSONStorage(() => sessionStorage),
}
)
createJSONStorage() : Zustand์ ์ํ๋ฅผ JSON ํํ๋ก ์์ ํ๊ฒ ๋ก์ปฌ ์คํ ๋ฆฌ์ง๋ ์ธ์
์คํ ๋ฆฌ์ง์ ์ ์ฅํ ์ ์๋๋ก ๋์์ฃผ๋ ๋ํผ ํจ์
๋ ์ง ๊ฐ์ฒด๋ ํน์ํ ํฌ๋งท์ด ์๋ค๋ฉด replacer, reviver๋ฅผ ์ถ๊ฐํ๋ฉด ์ข์
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
interface DateState {
someDate: Date;
setDate: (date: Date) => void;
}
export const useDateStore = create<DateState>()(
persist(
(set) => ({
someDate: new Date(),
setDate: (date) => set({ someDate: date }),
}),
{
name: 'date-store',
storage: createJSONStorage(() => sessionStorage, {
// ์ ์ฅํ ๋ Date๋ฅผ ๊ฐ์ฒด๋ก ๋ฐ๊ฟ
replacer: (key, value) => {
if (key === 'someDate') return { type: 'date', value }
return value
},
// ๋ถ๋ฌ์ฌ ๋ Date ๊ฐ์ฒด๋ก ๋ค์ ๋ณต์
reviver: (key, value) => {
if (value && value.type === 'date') return new Date(value.value)
return value;
},
}),
}
)
)
๐ง ์ฝ๋ ํด์
replacer
- ์ด ํจ์๋ ์ธ์ ํธ์ถ๋ ๊น?
- Zustand๊ฐ ์ํ๋ฅผ ์ ์ฅ์์ ์ ์ฅํ ๋
JSON.stringify()๋ฅผ ์- ์ด๋ ๊ฐ key-value ์๋ง๋ค ์ด
replacerํจ์๊ฐ ์๋์ผ๋ก ํธ์ถ๋จ- ๋ฌด์จ ์ผ์ ํ ๊น?
key === 'someDate'์ธ ํญ๋ชฉ, ์ฆ ๋ ์ง ๊ฐ์ ๋ง๋๋ฉด- ๋ฌธ์์ด๋ก ์ ์ฅํ์ง ์๊ณ , ํ์ ์ ๋ณด๋ฅผ ํฌํจํ ๊ฐ์ฒด๋ก ๊ฐ์ธ์ ์ ์ฅํจ
"someDate": { "type": "date", "value": "2025-05-13T14:00:00.000Z", }reviver
- ์ด ํจ์๋ ์ธ์ ํธ์ถ๋ ๊น?
- ์ํ๋ฅผ ๋ค์ ๋ถ๋ฌ์ฌ ๋
JSON.parse()๋ฅผ ์- ๊ฐ key-value ์๋ง๋ค ์ด
reviverํจ์๊ฐ ์๋์ผ๋ก ํธ์ถ๋จ- ๋ฌด์จ ์ผ์ ํ ๊น?
- ์ ์ฅ๋ ๋ฌธ์์ด์ ๋ถ๋ฌ์์ ๋, value๊ฐ
{type: 'date', value: '...'}ํํ๋ผ๋ฉด- ๊ทธ๊ฒ์ ๋ค์
new Date(value.value)๋ก ๊ฐ์ธ์ ์ง์ง Date ๊ฐ์ฒด๋ก ๋ณต์ํจ
โ
replacer/reviver๊ฐ ํ์ํ ์ด์
- ๋ธ๋ผ์ฐ์ ์ ์ฅ์๋ ์ค์ง ๋ฌธ์์ด๋ง ์ ์ฅํ ์ ์์
- ๊ทธ๋์
Date๊ฐ์ ๊ฐ์ฒด๋ฅผ ๊ทธ๋ฅ ์ ์ฅํ๋ฉด "2025-05-13T13:00:00.000Z"์ฒ๋ผ ๋ฌธ์์ด๋ก ๋ฐ๋- ๋ฌธ์ ์ : ๋ถ๋ฌ์ฌ ๋ ํ์ ์ด
Date์๋์งstring์ด์๋์ง ์ ์ ์์ โnew Date()๋ก ๋ค์ ๋ง๋ค์ด์ค์ผ ํจ- ๊ทธ๋์ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ๋ถํจ
replacer: ์ ์ฅํ ๋Date๋ฅผ{type: 'date', value: '...'}ํ์์ผ๋ก ๊ฐ์ธ์คreviver: ๋ถ๋ฌ์ฌ ๋ ๊ทธ๊ฑธ ๋ค์new Date()๋ก ๋ณต์ํจ
์ํ๋ฅผ ๋ฆฌ์
ํ๊ฑฐ๋ ์ ์ฅ์๋ฅผ ์ด๊ธฐํํ๊ณ ์ถ์ ๋๋ clearStorage๋ฅผ ์
import { useAuthStore } from './store/useAuthStore'
useAuthStore.persist.clearStorage();
๋๋ removeItem์ ์ฌ์ฉํ์ฌ ์ ๊ฑฐํด๋ ๋จ
sessionStorage.removeItem('date-store');
partialize๋ฅผ ์ด์ฉํด์ ์ ์ฅํ ์ํ ์ ํํ๊ธฐ์ํ๋ฅผ ์ผ๋ถ๋ง ์ ์ฅํ๊ณ ์ถ์ ๋๋ partialize ์ต์
์ ์ฌ์ฉํจ
persist(
(set, get) => ({
username: '',
token: '',
isLoggedIn: false,
setUsername: (name: string) => set({ username: name }),
// ...
}),
{
name: 'auth-storage',
partialize: (state) => ({ token: state.token }), // token๋ง ์ ์ฅ
}
)
persist๋ ์ํ ์ ์ฒด ์ค์์ token๋ง ์ ์ฅ์(๋ก์ปฌ ์คํ ๋ฆฌ์ง๋ ์ธ์
์คํ ๋ฆฌ์ง ๋ฑ)์ ์ ์ฅํจusername๊ณผ isLoggedIn์ ์ ์ฅ์์ ์ ์ฅ๋์ง ์๊ธฐ ๋๋ฌธ์, ์๋ก๊ณ ์นจ ์ ์ด๊ธฐ๊ฐ(''์ false)์ผ๋ก ๋์๊ฐserialize : ์ ์ฅ๋๋ ๊ฐ์ ๋ฌธ์์ด๋ก ๋ฐ๊พธ๋ ๊ณผ์ deserialize : ๋ค์ ๋ถ๋ฌ์ฌ ๋ ํด์ํ๋ ๊ณผ์ persist(
(set) => ({ /* ์ํ*/ }),
{
name: 'custom-storage',
serialize: (state) => btoa(JSON.stringify(state)), // Base64 ์ธ์ฝ๋ฉ
deserialize: (str) => JSON.parse(atob(str)),
}
)// src/stores/favoriteList.store.ts
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
type FavoriteItem = {
placeName: string;
placeImg: string;
};
interface FavoriteListState {
favoriteList: FavoriteItem[];
addFavoriteList: (placeName: string, placeImg: string) => void;
deleteFavoriteList: (placeName: string, placeImg: string) => void;
resetFavoriteList: () => void;
}
export const useFavoriteListStore = create<FavoriteListState>()(
persist(
(set, get) => ({
favoriteList: [],
addFavoriteList: (placeName: string, placeImg: string) => {
const currentList = get().favoriteList;
// ์ค๋ณต ์ถ๊ฐ ๋ฐฉ์ง
const isDuplicate = currentList.some(
(item) => item.placeName === placeName && item.placeImg === placeImg);
if (isDuplicate) return;
set({ favoriteList: [...currentList, { placeName, placeImg }]});
},
deleteFavoriteList: (placeName: string, placeImg: string) => {
const currentList = get().favoriteList;
set({
favoriteList: currentList.filter((item) => item.placeName !== placeName && item.placeImg !== placeImg),
});
},
resetFavoriteList: () => set({ favoriteList: [] }),
}),
{
name: 'favorite-list',
storage: createJSONStorate(() => sessionStorage),
}
)
)
์ ๋ณด ํ์ ํ์ด์ง์์ ๊ฒฝ๋ณต๊ถ์ ์ฅ๋ฐ๊ตฌ๋์ ๋ด๊ณ ,

์ธ์
์คํ ๋ฆฌ์ง๋ฅผ ํ์ธํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๋ด์ฉ์ด ์ ๋ค์ด๊ฐ ๊ฒ์ ๋ณผ ์ ์๋ค.
