やりたいこと
redux-persistで永続化ストアの構造を変えたときに、古い構造のストアがlocalstorageから読み込まれてしまう。
ライブラリに用意されているcreateMigrate
という関数を使えば永続化ストアの移行ができるので、やり方を記載する。
結論
- persistConfigに
version
,migrate
を追加しろ - createMigrateに移行後のバージョンのストア定義を渡せ
const migrations = {
1: (state: any) => {
return {
...state,
post: {
migrateTest: 'uno',
},
};
},
}
const persistConfig = {
key: 'root',
storage,
whitelist: ['post'],
version: 1,
migrate: createMigrate(migrations, { debug: true }),
};
const reducer = persistReducer(persistConfig, rootReducer);
以下詳細を記載します。
前提
post
というストアだけをlocalstorageに永続化している。
// 永続化設定部分のみ抜粋
const persistConfig = {
key: 'root',
storage,
whitelist: ['post'],
};
const reducer = persistReducer(persistConfig, rootReducer);
post
ストアの構造はこういった形。
// 構造定義のみ抜粋
const initialState: InitialState = {
title: '',
body: '',
};
構造を変えてみる
migrateTest
というキーを追加してみる。
この状態で変更を反映しても、redux-persistはlocalstorageからストアを復元するため、migrateTest
が追加されない。
const initialState: InitialState = {
title: '',
body: '',
migrateTest: '0', // 追加
};
migrationする
さて本題だが、redux-persistが用意してくれているcreateMigrate
関数を使ってマイグレーションを行う。
configのversionを上げる
まずはpersistReducer
の第一引数に渡すコンフィグに、version
というキーでnumberを渡す。
redux-persistはlocalstorageのversion
と、configのversion
を見比べて、バージョンが上がっていたらマイグレーションを実行する。
ちなみにconfigにバージョンを指定しないと、「-1」がlocalstorageに記録される。
const persistConfig = {
key: 'root',
storage,
whitelist: ['post'],
version: 1, // 追加
};
migrationManifestを指定する
続いて、コンフィグにmigrate
というキーでcreateMigrate
の返り値を渡す。
createMigrate
は第一引数にmigrationManifest
、第二引数に{debug: boolean}
型のオブジェクトを取る。
{debug: true}
にすると、ならコンソールにマイグレーションのログが表示されるようになる。
ちなみに、{debug: true}
であってもNODE_ENV
がproduction
ならログは表示されない。
const migrations = {
// 後述
}
const persistConfig = {
key: 'root',
storage,
whitelist: ['post'],
version: 1,
migrate: createMigrate(migrations, { debug: true }),
};
migrationManifest
はstring型のキーと、stateを取ってstateを返す関数のバリューを持つオブジェクト。
interface MigrationManifest {
[key: string]: (state: PersistedState) => PersistedState;
}
そして、migrationManifestを記述する。
キーはマイグレーション後のバージョン。引数のstateにはlocalStorageから復元したstateが入る。
スプレッド構文で一旦展開し、上書きしたいストアを定義し直す。
const migrations = {
1: (state: any) => {
return {
...state,
post: {
migrateTest: 'uno',
},
};
},
}
まとめると、以下のようなコードになる。
const migrations = {
1: (state: any) => {
return {
...state,
post: {
migrateTest: 'uno',
},
};
},
}
const persistConfig = {
key: 'root',
storage,
whitelist: ['post'],
version: 1,
migrate: createMigrate(migrations, { debug: true }),
};
ちなみにmigrationsには過去のバージョンを残さなくても問題ない。
上記の例で言えば、バージョン2に上げたくなったら1
のオブジェクトを消しても構わない。
const migrations = {
- 1: (state: any) => {
- return {
- ...state,
- post: {
- migrateTest: 'uno',
- },
- };
- },
+ 2: (state: any) => {
+ return {
+ ...state,
+ post: {
+ migrateText: 'dos',
+ },
+ };
+ }
}
複数のバージョンをmigrationManifest
に記載すると順にstateを書き換えていくため、場合によっては過去のバージョンを残すやり方もある。
(個人的には最新バージョンだけ定義する書き方のほうがバグが少なそうに思えます。)
1: (state: any) => {
return {
...state,
post: {
migrateTest: 'uno',
},
};
},
2: (state: any) => {
return {
...state,
post: {
title: 'タイトル',
},
};
}
=> state:
{
title: 'タイトル',
migrateText: 'uno',
}
Appendix