Context
, Jotai
, Recoil
, Redux
, Zustand
상태 관리 라이브러리에 각각 LocalStorage를 적용해 보았습니다.
useReducer를 사용하여 커스텀 state를 만들수 있습니다. todo가 변할 때 마다 LocalStorage에 저장하여 reducer를 만들고, context에 저장하여 필요할 때 바로 꺼내 쓰도록 했습니다.
// useLocalStorageReducer.ts
import { useEffect, useReducer } from "react";
export const useLocalStorageReducer = (key, defaultValue, todoReducer) => {
const [todos, dispatch] = useReducer(todoReducer, defaultValue, () => {
let value;
try {
value = JSON.parse(localStorage.getItem(key)) || [];
} catch (error) {
value = defaultValue;
}
return value;
})
useEffect(() => {
localStorage.setItem(key, JSON.stringify(todos));
}, [todos, key]);
return [todos, dispatch];
}
⭐️ 적용 코드 보러가기: https://github.com/wwowww/projects/commit/7583c724b0dd9f453ad094a9edcfb0a7cd7adcf7
Jotai에 persist가 따로 있어 localstorage를 쉽게 사용할줄 알았으나 없다고 합니다. 😂
테마 설정 은 import { atomWithStorage } from 'jotai/utils'
을 통해 쉽게 구현 가능합니다.
import { useAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
const theme = atomWithStorage('dark', false);
export default function Page() {
const [appTheme, setAppTheme] = useAtom(theme);
const handleClick = () => setAppTheme(!appTheme);
return (
<div className={appTheme? 'dark': 'light'}>
<h1>This is a theme switcher</h1>
<button onClick={handleClick}>{appTheme? 'DARK': 'LIGHT'}</button>
</div>
)
}
setTodos((prev) => {
const newTodos = [...prev, todo];
localStorage.setItem(localStorageKey, JSON.stringify(newTodos));
return newTodos;
});
저는 위와 같이 모든 코드에 localStorage.setItem(localStorageKey, JSON.stringify(newTodos));
을 넣어 구현하였습니다.
⭐️ 적용 코드 보러 가기: https://github.com/wwowww/projects/commit/211ca00617e575eccd5f2ed80c99ee8f80f91d95
Recoil은 Recoil-persist 라이브러리를 사용하면 손쉽게 LocalStorage에 저장할 수 있습니다!
import { recoilPersist } from 'recoil-persist';
Recoil-persist를 사용하면 이전처럼 새로고침을 해도 recoil state가 날라가지 않고 sessionStorage 또는 localStorage에 보관됩니다.
아무런 설정도 해주지 않으면 key는 'recoil-persist', 저장소는 localStorage에 기본적으로 저장됩니다.
Redux도 persist가 있습니다.
import { persistReducer, persistStore } from "redux-persist";
import { Provider } from "react-redux";
import ReduxTodoInput from "@/prototypes/Redux/ReduxTodoInput";
import ReduxTodoList from "@/prototypes/Redux/ReduxTodoList";
import { persistor, store } from '@/store/reduce';
import { PersistGate } from 'redux-persist/integration/react';
const ReduxTodoPage = () => {
return (
<Provider store={store}>
<PersistGate persistor={persistor}>
<ReduxTodoInput />
<ReduxTodoList />
</PersistGate>
</Provider>
)
}
위 코드처럼 <PersistGate persistor={persistor}>
로 감싸주면 됩니다.
redux-persist로 LocalStorage를 설정하는 과정에서 콘솔창에 에러가 생겼는데, 바로 A non-serializable value was detected in an action, in the path: register.
라는 에러였습니다.
Redux를 사용할 때는 직렬화할 수 없는 값을 상태나 액션에 넣지 않아야 합니다. **하지만 redux-persist를 사용하는 경우 **이러한 핵심 원칙에 예외가 발생하기 때문에 오류 메시지에 나온 register를 포함해 dispatch하는 모든 액션 유형을 무시해야 합니다.
아래 코드처럼 ignoredActions에 [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
를 추가하여 오류를 해결했습니다.
mport { FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER } from "redux-persist";
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});
Zustand에는 persist가 내장되어 있어 쉽게 구현할 수 있었습니다.
import { persist } from 'zustand/middleware';
const useZustandTodo = create(persist<States & Actions>((set) => ({
todos: [],
addTodo: (todo) => {
set((state) => ({ todos: [...state.todos, todo], }));
},
...
{
name: 'zustandTodoStore',
getStorage: () => {
return localStorage;
},
},
));
export default useZustandTodo;
LocalStorage를 여러가지 상태 관리 라이브러리에 적용해 보았습니다. Persist가 있는 상태 관리 라이브러리는 비교적 쉽게 적용 가능했지만, jotai의 경우 persist가 존재함에도 LocalStorage 자체의 기능은 제공하지 않아 따로 구현해야 했습니다.
Zustand의 경우 내장된 기능으로 존재했고, redux와 recoil의 경우에는 각각 recoil-persist
, redux-persist
를 설치해줘야 했습니다.
실제 프로젝트 사용시에는 라이브러리 마다 크기, 구성원들의 숙련도, 업데이트 유무(Recoil은 현재 시점에서 아직 1.0.0이 배포되지 않아 안정성이나 성능, 사용성 등을 보장하기는 쉽지 않다고 합니다) 등의 사항을 고려하여 상태 라이브러리를 채택해 사용해야 겠습니다.
⭐️ 적용 코드 보러 가기 링크를 클릭하시면 아래 사진처럼 자세한 코드를 보실 수 있습니다 🙂