소개.
Bitfolio 앱 프로젝트를 진행하면서, 서버 데이터베이스가 아닌 로컬의 스토리지에 핵심 데이터를 저장하는 구조를 채택하게 되었습니다.
그로 인해 기존 사용자들의 데이터를 최대한 안정적으로 보존하면서 마이그레이션을 진행해야 했고, 이 과정에서 꽤 신중하게 접근해야 했습니다. 시행착오도 있었지만, 그 경험을 정리해 공유해보고자 합니다.
redux-persist를 사용해 persisted state를 관리하다 보면, 종종 state의 기본값을 수정하거나 새로운 { key: value } 를 추가하거나 제거해야 할 상황이 발생합니다.
이럴 때 필요한 것이 바로 마이그레이션(migration) 작업입니다.
이번 포스팅에서는 redux-persist에서 마이그레이션을 적용하는 방법에 대해 소개하겠습니다.
전제 조건.
- 기본적인 redux-persist의 구성은 이전 포스터에서 설명한 구성을 바탕으로 설명합니다.
- redux의 배경지식이 필요합니다.
migration하기 전 store 배경
// /src/store/slices/baseSetting.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface BaseSettingState extends ChartSettingState {
localScheme: 'dark' | 'light' | 'system'; // 앱 설정 scheme
currency: 'krw' | 'usd' | 'eur'; // 앱 설정 통화
chartOptions: {
interval: number;
}
}
export const initialState: BaseSettingState = {
localScheme: 'dark',
currency: 'krw',
chartOptions: {
interval: 1
}
};
export const baseSettingSlice = createSlice({
name: 'baseSetting',
initialState,
reducers: {
// ...reducers
}
});
export const {
// ...reducers
} = baseSettingSlice.actions;
export default baseSettingSlice.reducer;
위와 같은 reducer가 존재할 때, AsyncStorage를 예로 들어보겠습니다.
// Reducer
baseSetting: {
localScheme: 'dark',
currency: 'krw',
chartOptions: {
interval: 1
},
}
baseSetting reducer에 새로운 { key: value } 를 다음과 같이 추가하면 어떻게 될까요?
// Reducer
baseSetting: {
localScheme: 'dark',
currency: 'krw',
chartOptions: {
interval: 1,
type: 'line', // <--- 추가
},
}
useSelector를 통해 추가한 key(chartOptions.type)의 값을 불러올 때 기대했던 'line'이 아닌 undefined가 올 것입니다.
이것을 해결하기 위해서는 migration이 필요합니다.
// /src/store/slices/baseSetting.ts
// ...기존 코드 skip
interface BaseSettingState extends ChartSettingState {
localScheme: 'dark' | 'light' | 'system'; // 앱 설정 scheme
currency: 'krw' | 'usd' | 'eur'; // 앱 설정 통화
chartOptions: {
interval: number;
type: 'line' | 'candlestic'; // <--- 추가
}
}
export const initialState: BaseSettingState = {
localScheme: 'dark',
currency: 'krw',
chartOptions: {
interval: 1,
type: 'line', // <--- 추가
}
};
// ...기존 코드 skip
states migration 적용하기.
persisted states를 migration 하는 데에는 다음과 같이 데이터베이스 migration 방식으로 수행됩니다.
Wikipedia 설명에 따르면, 데이터베이스의 스키마를 최신 또는 이전 버전으로 업데이트하거나 되돌릴 필요가 있을 때마다 데이터베이스에서 스키마 마이그레이션이 수행됩니다. 마이그레이션은 스키마 마이그레이션 도구를 사용하여 프로그래밍 방식으로 수행됩니다
따라서 redux-persist의 storage인 AsyncStorage를 key와 value로 이루어진 작은 데이터베이스라고 생각하시면 쉽습니다.
이제 이전에 작성했던 포스터에 구성에 덧붙여 migration 적용하기까지 코드를 업데이트해봅시다.
// src/store/index.ts
// 이전에 작성되었던 코드는 skip합니다.
import {
// ...기존 코드들
createMigrate,
} from 'redux-persist';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
import migrations from './persistMigrations';
const persistConfig: PersistConfig<ReducersState> = {
key: 'root',
storage: AsyncStorage,
version: 0, // 이전에 기본 version으로 -1이 할당되어 있었기 때문에 0으로 update해주겠습니다.
migrate: createMigrate(migrations as any, { debug: true }),
stateReconciler: autoMergeLevel2,
whitelist: ['baseSettingReducer'], // persist store에 저장 할 reducer들
blacklist: ['globalStateReducer'], // persist store에 저장하지 않을 reducer들
};
migration 할 때 이전 버전보다 버전을 높이는 것이 중요합니다.
핵심은 이렇습니다.
- 버전 키 추가 및 초기화
version 키를 추가하기 전까지는 기본값으로 -1이 설정되므로, 최초 마이그레이션 버전인 0으로 지정해주어야 합니다. - stateReconciler 설정
stateReconciler로 autoMergeLevel2를 설정하였습니다. 해당 옵션의 동작 방식에 대해서는 공식 문서를 참고해 주세요. - 마이그레이션 함수 설정
migrate 옵션에는 아래에서 작성할 마이그레이션 객체를 기반으로 생성한 함수를 지정합니다.
Migration 작성
잘 작성된 예시 문서가 존재하니 같이 참고하시면 좋겠습니다.
// /src/store/persistMigrations.ts
import { ApplicationState } from './reducers';
import { initialState as baseSettingInitState } from './slices/baseSetting';
/**
* Redux store migrations.
*/
export default {
0: (state: ApplicationState): ApplicationState => {
return {
...state,
baseSetting: {
...state.baseSetting,
chartOptions: {
...state.baseSetting.chartOptions,
type: 'line', // <--- 추가된 key value
},
},
};
},
};
이 마이그레이션 함수는 version이 0으로 업데이트될 때 호출되며, 기존 상태를 얕은 복사(shallow copy)를 통해 불변성을 유지하면서 새로운 key인 type: 'line'을 chartOptions에 추가합니다.
이제 모든 준비가 완료되었습니다.
앱을 리로드하면, 마이그레이션된 새로운 reducer 구조에 맞춰 상태가 자동으로 동기화됩니다.
처음에는 이러한 마이그레이션 환경을 구성하는 데 다소 어려움을 겪을 수 있습니다. 하지만 한 번 세팅을 완료하고 나면, 이후의 마이그레이션 작업은 훨씬 수월하게 진행할 수 있습니다.
스토리지 버전 관리와 마이그레이션 설정은 redux-persist를 사용할 때 반드시 이해하고 활용해야 할 핵심 개념입니다.
이 글이 해당 개념을 이해하는 데 도움이 되었기를 바랍니다.
'사이드 프로젝트 > Bitfolio' 카테고리의 다른 글
redux-persist storage로 AsyncStorage 사용하고 redux-toolkit까지 구성해보기 (0) | 2022.03.09 |
---|---|
사이드 프로젝트로 앱 주제 선정하기. (0) | 2022.01.12 |