redux-persist states migration하기.

2022. 3. 9. 22:09·사이드 프로젝트/Bitfolio

소개.

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 할 때 이전 버전보다 버전을 높이는 것이 중요합니다.

 

핵심은 이렇습니다.

  1. 버전 키 추가 및 초기화
    version 키를 추가하기 전까지는 기본값으로 -1이 설정되므로, 최초 마이그레이션 버전인 0으로 지정해주어야 합니다.
  2. stateReconciler 설정
    stateReconciler로 autoMergeLevel2를 설정하였습니다. 해당 옵션의 동작 방식에 대해서는 공식 문서를 참고해 주세요.
  3. 마이그레이션 함수 설정
    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
'사이드 프로젝트/Bitfolio' 카테고리의 다른 글
  • redux-persist storage로 AsyncStorage 사용하고 redux-toolkit까지 구성해보기
  • 사이드 프로젝트로 앱 주제 선정하기.
heesu
heesu
개발하면서 겪었던 경험을 공유합니다.
  • heesu
    heesu dev
    heesu
  • 전체
    오늘
    어제
    • 분류 전체보기
      • Nodejs로 알고리즘 박살내기
      • 사이드 프로젝트
        • Bitfolio
      • React Native
      • Etc
      • HTML
      • NextJS
      • Javascript
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Nodejs
    asyncstorage
    백준
    투 포인터
    완전 탐색
    JavaScript
    벨만포드 알고리즘
    정렬
    Dijkstra
    BFS
    UnionFind
    certbot
    학습
    lv4
    최소 스패닝
    크루스칼 알고리즘
    react-native
    redux-persist
    부분합
    우선순위 큐
    크루스칼
    프로그래머스
    union-find
    cleancode
    dfs
    letsencrypt
    알고리즘
    시맨틱 태그
    Animated
    React Native
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
heesu
redux-persist states migration하기.
상단으로

티스토리툴바