- Published on
- reading time
- 6분
React Native: Redux-toolkit과 redux-persist로 앱 상태 영구 저장하기
- Authors
- Name
- Heesu Choi
- dqdq4197@gmail.com
- Github
- dqdq4197
React Native 앱을 만들다 보면 상태(state)가 앱을 재시작할 때 초기화되는 문제를 마주합니다.
사용자 설정이나 장바구니처럼 앱이 종료되어도 유지되어야 하는 데이터를 저장하려면 로컬 저장소가 필요합니다.
이번 글에서는 사용자 언어 설정과 다크 모드 설정을 예제로 사용합니다.
- 사용자가 선택한 언어는 앱을 다시 열었을 때도 그대로 유지되어야 하고,
- 다크 모드 설정도 앱 재시작 시 초기화되지 않아야 사용자 경험이 일관됩니다.
이러한 이유로 Redux 상태를 영구 저장(persist) 해야 하며,
이를 위해 redux-persist
와 React Native 환경에서 권장되는 AsyncStorage
를 storage 엔진으로 사용하고, redux-toolkit
까지 함께 구성하는 방법을 설명합니다.
TIP
왜 AsyncStorage를 쓸까?
웹 환경에서는 보통 localStorage
나 sessionStorage
를 사용합니다.
하지만 React Native에는 DOM API가 없기 때문에, 공식적으로는 AsyncStorage를 사용합니다.
- 비동기 API로 동작하므로 대용량 데이터도 안정적으로 처리 가능
- iOS/Android 모두 지원
- 커뮤니티에서 관리되는 안정적인 패키지:
@react-native-async-storage/async-storage
Package Dependencies
npm install react-redux
npm install redux-persist
npm install @reduxjs/toolkit
npm install @react-native-async-storage/async-storage
npm install -D @types/react-redux
Redux Persist 적용
1. slice 예제: settingsSlice
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface SettingsState {
darkMode: boolean;
language: 'en' | 'ko';
}
const initialState: SettingsState = {
darkMode: false,
language: 'ko',
};
const settingsSlice = createSlice({
name: 'settings',
initialState,
reducers: {
toggleDarkMode(state) {
state.darkMode = !state.darkMode;
},
setLanguage(state, action: PayloadAction<'en' | 'ko'>) {
state.language = action.payload;
},
},
});
export const { toggleDarkMode, setLanguage } = settingsSlice.actions;
export default settingsSlice.reducer;
이 slice는 앱의 사용자 환경설정 을 관리합니다.
darkMode
: 사용자가 다크 모드를 선택했는지 여부language
: 사용자가 선택한 앱 언어
Redux Toolkit의 slice로 정의되어 있어 reducer와 action이 함께 포함되어 있으며, Redux Persist + AsyncStorage와 결합하면 앱을 종료하고 다시 열어도 사용자의 설정이 그대로 유지됩니다.
예제에서는 toggleDarkMode
와 setLanguage
액션을 통해 상태를 변경할 수 있습니다.
2. reducer 구성 및 타입 정의
여러 slice reducer를 하나의 루트 리듀서로 관리합니다.
import settingsReducer from './slices/settings'
export interface ApplicationState {
settings: ReturnType<typeof settingsReducer>
}
const reducers = {
settingsReducer,
}
export default reducers
3. Store와 Persistor 생성
import { combineReducers } from 'redux'
import { configureStore } from '@reduxjs/toolkit'
import { persistStore, persistReducer, PersistConfig } from 'redux-persist'
import AsyncStorage from '@react-native-async-storage/async-storage'
import reducers from './reducers'
const rootReducer = combineReducers({ ...reducers })
type ReducersState = ReturnType<typeof rootReducer>
const persistConfig: PersistConfig<ReducersState> = {
key: 'root', // 저장될 때 식별자로 쓰이는 key
storage: AsyncStorage, // redux-persist storage로 AsyncStorage 사용
whitelist: ['settingsReducer'], // 저장할 reducer
// blacklist: ['tempReducer'], // 저장하지 않을 reducer
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = configureStore({
reducer: persistedReducer,
})
const persistor = persistStore(store)
export { store, persistor }
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
TIP
- whitelist: 반드시 저장하고 싶은 reducer 목록을 지정합니다.
- blacklist: 저장하지 않을 reducer를 지정합니다.
둘 중 하나만 선택적으로 사용하면 됩니다.
4. Provider와 PersistGate 설정
import React from 'react'
import { Provider } from 'react-redux'
import { Text } from 'react-native'
import { PersistGate } from 'redux-persist/integration/react'
import { store, persistor } from '/store'
import RootNavigation from '/navigators/Root'
const LoadingView = () => <Text>Loading...</Text>
function App() {
return (
<Provider store={store}>
<PersistGate loading={LoadingView} persistor={persistor}>
<RootNavigation />
</PersistGate>
</Provider>
)
}
export default App
Provider
: Redux store를 리액트 컴포넌트 트리에 주입합니다.PersistGate
: persisted state가 불러와질 때까지 UI 렌더링을 지연시키고, loading prop에 지정한 컴포넌트를 대신 보여줍니다.
마무리
이렇게 구성하면 Redux 상태가 앱을 종료하거나 새로고침해도 유지됩니다.
- Redux Toolkit으로 store 관리가 단순해지고,
- Redux Persist + AsyncStorage로 영구 저장까지 가능합니다.
다음에는 persisted state의 기본값 변경이나 key-value 추가/삭제 시 필요한 migration 관련 내용을 다뤄보겠습니다.