Published on
·
reading time
6분

React Native: Redux-toolkit과 redux-persist로 앱 상태 영구 저장하기

Authors

React Native 앱을 만들다 보면 상태(state)가 앱을 재시작할 때 초기화되는 문제를 마주합니다.
사용자 설정이나 장바구니처럼 앱이 종료되어도 유지되어야 하는 데이터를 저장하려면 로컬 저장소가 필요합니다.

이번 글에서는 사용자 언어 설정다크 모드 설정을 예제로 사용합니다.

  • 사용자가 선택한 언어는 앱을 다시 열었을 때도 그대로 유지되어야 하고,
  • 다크 모드 설정도 앱 재시작 시 초기화되지 않아야 사용자 경험이 일관됩니다.

이러한 이유로 Redux 상태를 영구 저장(persist) 해야 하며,
이를 위해 redux-persist와 React Native 환경에서 권장되는 AsyncStorage를 storage 엔진으로 사용하고, redux-toolkit까지 함께 구성하는 방법을 설명합니다.

TIP

왜 AsyncStorage를 쓸까?

웹 환경에서는 보통 localStoragesessionStorage를 사용합니다.
하지만 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

src/store/slices/settings.ts
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와 결합하면 앱을 종료하고 다시 열어도 사용자의 설정이 그대로 유지됩니다.

예제에서는 toggleDarkModesetLanguage 액션을 통해 상태를 변경할 수 있습니다.


2. reducer 구성 및 타입 정의

여러 slice reducer를 하나의 루트 리듀서로 관리합니다.

src/store/reducers.ts
import settingsReducer from './slices/settings'

export interface ApplicationState {
  settings: ReturnType<typeof settingsReducer>
}

const reducers = {
  settingsReducer,
}

export default reducers

3. Store와 Persistor 생성

src/store/index.ts
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 설정

src/App.ts
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 관련 내용을 다뤄보겠습니다.