redux-persistで永続化したストアをmigrationする

## やりたいこと
redux-persistで永続化ストアの構造を変えたときに、古い構造のストアがlocalstorageから読み込まれてしまう。
ライブラリに用意されている`createMigrate`という関数を使えば永続化ストアの移行ができるので、やり方を記載する。

## 結論
1. persistConfigに`version`, `migrate`を追加しろ
2. createMigrateに移行後のバージョンのストア定義を渡せ

```rootRedux.ts
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に永続化している。

```rootRedux.ts
// 永続化設定部分のみ抜粋
const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['post'],
};
const reducer = persistReducer(persistConfig, rootReducer);
```

`post`ストアの構造はこういった形。

```postRedux.ts
// 構造定義のみ抜粋
const initialState: InitialState = {
  title: '',
  body: '',
};
```

## 構造を変えてみる
`migrateTest`というキーを追加してみる。
この状態で変更を反映しても、redux-persistはlocalstorageからストアを復元するため、`migrateTest`が追加されない。

```postRedux.ts
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に記録される。

```rootReducer.ts
const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['post'],
  version: 1, // 追加
};
```

### migrationManifestを指定する

続いて、コンフィグに`migrate`というキーで`createMigrate`の返り値を渡す。
`createMigrate`は第一引数に`migrationManifest`、第二引数に`{debug: boolean}`型のオブジェクトを取る。
`{debug: true}`にすると、ならコンソールにマイグレーションのログが表示されるようになる。

ちなみに、`{debug: true}`であっても`NODE_ENV`が`production`ならログは表示されない。

```rootReducer.ts

const migrations = {
  // 後述
}
const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['post'],
  version: 1,
  migrate: createMigrate(migrations, { debug: true }),
};
```

`migrationManifest`はstring型のキーと、stateを取ってstateを返す関数のバリューを持つオブジェクト。

```redux-persist/types/types.d.ts
interface MigrationManifest {
  [key: string]: (state: PersistedState) => PersistedState;
}
```

そして、migrationManifestを記述する。
キーはマイグレーション後のバージョン。引数のstateにはlocalStorageから復元したstateが入る。
スプレッド構文で一旦展開し、上書きしたいストアを定義し直す。

```rootRedux.ts
const migrations = {
  1: (state: any) => {
    return {
      ...state,
      post: {
        migrateTest: 'uno',
      },
    };
  },
}
```

まとめると、以下のようなコードになる。

```rootRedux.ts
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
https://dev.to/sasurau4/how-to-migrate-store-persisted-by-redux-persist-5dng