npm package: @h5plus/persist
Description: 基于Vuex的持久化保存，加入了HTML5+ 的 plus.storage 支持
Author: cwb
Latest version: 0.1.1

# persist

> `基于Vuex的持久化保存，加入了HTML5+ 的 plus.storage 支持`，参考了 vuex-persist

## 安装

```shell
npm install @h5plus/persist --save
# or
yarn add @h5plus/persist
```

## 使用

### 步骤

1.导入

```js
import persist from '@h5plus/persist'
```

2.创建对象

```js
const vuexLocal = new VuexPersistence({
  storage: window.localStorage
})
```

3.作为 Vuex plugin.

```js
const store = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  plugins: [vuexLocal.plugin]
}
export default vuexStore
```

### 构造参数

创建对象时的参数对象 `options`

| 属性            | 类型                                   | 说明                                                                                                                                             |
| --------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| key             | string                                 | 在 storage 中存储对应的键值 <br>_**Default: 'vuex'**_                                                                                            |
| delKey          | string                                 | 需要删除 storage 中存储对应的键值，一般用于换库的时候<br>_**Default: ''**_                                                                       |
| storage         | Storage (Web API)                      | localStorage, sessionStorage, localforage 或自定义的存储对象. <br>必须有 getItem, setItem, clear 等方法. <br> _**Default: window.localStorage**_ |
| plus            | boolean                                | 是否 HTML5+ API 中的存储对象（同步存储）： plus.storage <br> _**Default: false**_                                                                |
| needEvent       | boolean                                | 是否需要跨窗体的事件通知 <br> _**Default: true**_                                                                                                |
| saveState       | function<br> (key, state[, storage])   | 持久化保存 state，可以自定义                                                                                                                     |
| restoreState    | function<br> (key[, storage]) => state | 从持久化存储中读取 state，可以自定义                                                                                                             |
| reducer         | function<br> (state) => object         | State reducer. 仅保存需要保存的值。 <br>默认情况下，保存整个 state，如果指定了 modules 则只保存列表中的值；或者自定义                            |
| filter          | function<br> (mutation) => boolean     | Mutation filter. 可以针对 `mutation.type` 进行过滤 <br>只为那些想要持久化写入的对象触发。 <br> Default 所有的 mutations 返回 true                |
| modules         | string[]                               | 要持久化的模块列表。（使用它，你可用不用自定义 reducer）                                                                                         |
| asyncStorage    | boolean                                | 是否异步存储，意味着 store 使用 Promises (例如 localforage) <br>_**Default: false**_                                                             |
| supportCircular | boolean                                | 表示该状态是否具有自身的循环引用 (state.x === state)                                                                                             |

### 使用说明

#### Reducer

自定义 reducer 不应该改变 state 的结构。

```javascript
const persist = new VuexPersistence({
  reducer: (state) => state.products,
  ...
})
```

上面的代码就是错的 **wrong** <br>
应该这么做：

```js
const persist = new VuexPersistence({
  reducer: (state) => ({products: state.products}),
  ...
})
```

#### Circular States

如果你的 state 中有循环引用的结构

```js
let x = { a: 10 }
x.x = x
x.x === x.x.x // true
x.x.x.a === x.x.x.x.a //true
```

`JSON.parse()` and `JSON.stringify()` 将不能正常工作.
你需要安装 `circular-json`

```
npm install circular-json
```

并且在构造 store 时, 添加 `supportCircular`

```js
new VuexPersistence({
  supportCircular: true,
  ...
})
```

## 示例

### 简单的

```js
import Vue from 'vue'
import Vuex from 'vuex'
import VuexPersistence from '@h5plus/persist'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    user: { name: 'Arnav' },
    navigation: { path: '/home' }
  },
  plugins: [new VuexPersistence().plugin]
})

export default store
```

### 多实例的

#### 示例 1

```js
import Vue from 'vue'
import VuexPersistence from 'vuex-persist'
import Cookies from 'js-cookie'
import { module as userModule } from './user'
import navModule from './navigation'

Vue.use(Vuex)

const vuexCookie = new VuexPersistence({
  restoreState: (key, storage) => Cookies.getJSON(key),
  saveState: (key, state, storage) =>
    Cookies.set(key, state, {
      expires: 3
    }),
  // 只保存 user 模块
  modules: ['user'],
  // 过滤：只处理类型为 'logIn' 或 'logOut' 的变更
  filter: mutation => mutation.type == 'logIn' || mutation.type == 'logOut'
})
const vuexLocal = new VuexPersistence({
  storage: window.localStorage,
  // 只保存 navigation 模块
  reducer: state => ({ navigation: state.navigation }),
  // 过滤：只处理类型为 'laddNavItem' 的变更
  filter: mutation => mutation.type == 'addNavItem'
})

const store = new Vuex.Store({
  modules: {
    user: userModule,
    navigation: navModule
  },
  plugins: [vuexCookie.plugin, vuexLocal.plugin]
})

export default store
```

#### 示例 2

```js
import VuexPersistence from '@h5plus/persist'

import localForage from 'localforage'
const vuexLocal = new VuexPersistence({
  // 使用 localForage
  storage: localForage,
  // 只保存 rates 模块
  modules: ['rates'],
  // 过滤：只处理类型为 'rateUpsert' 的变更
  filter: mutation => {
    return mutation.type == 'rateUpsert'
  }
})

const baseData = new VuexPersistence({
  key: 'base',
  // 只保存 'coins', 'config' 模块
  modules: ['coins', 'config'],
  // 过滤：只处理类型为 'coinsUpsert' 或 'walletUpsert' 的变更
  filter: mutation => {
    return mutation.type == 'coinsUpsert' || mutation.type == 'walletUpsert'
  }
})

const recordsData = new VuexPersistence({
  // 使用 plus.storage
  plus: true,
  key: 'records',
  // 只保存 records 模块
  modules: ['records'],
  // 过滤：只处理类型为 'rateUpsert' 的变更
  filter: mutation => {
    return mutation.type == 'recordsUpsert'
  }
})

const vuexStore = new Vuex.Store({
  state,
  getters,
  mutations,
  actions,
  plugins: [baseData.plugin, recordsData.plugin, vuexLocal.plugin]
})

export default vuexStore
```

### 严格模式

支持 [Vuex 严格模式](https://vuex.vuejs.org/zh/guide/strict.html)
(注意, **不要在发布环境下启用严格模式！**)
在严格模式, 不能使用 `store.replaceState` ，所以使用一个变更(mutation)来代替

你需要添加 **`RESTORE_MUTATION`** 到你的 mutations

看下面示例

配置严格模式支持

```js
import Vue from 'vue'
import Vuex from 'vuex'
import VuexPersistence from 'vuex-persist'

const vuexPersist = new VuexPersistence({
  // This **MUST** be set to true
  strictMode: true,
  storage: localStorage,
  reducer: state => ({ dog: state.dog }),
  filter: mutation => mutation.type === 'dogBark'
})

const store = new Vuex.Store({
  // This makes the Vuex store strict
  strict: true,
  state: {
    user: {
      name: 'Arnav'
    },
    foo: {
      bar: 'baz'
    }
  },
  mutations: {
    // 这个变更 **必须** 命名为 "RESTORE_MUTATION"
    RESTORE_MUTATION: vuexPersist.RESTORE_MUTATION
  },
  plugins: [vuexPersist.plugin]
})
```

一些常用的 store

- **[js-cookie](https://npmjs.com/js-cookie)** 使用浏览器 Cookie
- **window.localStorage** (PC 重启也会保留, 知道你清理浏览器数据)
- **window.sessionStorage** (关闭浏览器选项卡时消失)
- **[localForage](https://localforage.docschina.org/)** 使用浏览器中的 IndexedDB 数据库
- **[plus storage](http://www.html5plus.org/doc/zh_cn/storage.html)** Storage 模块管理应用本地数据存储区，用于应用数据的保存和读取

### 关于 LocalForage 和异步存储的注意事项

HTML5 DOM 规范定义的 Window.Storage API，它实现了以下功能 -

```typescript
interface Storage {
  readonly length: number
  clear(): void
  getItem(key: string): string | null
  key(index: number): string | null
  removeItem(key: string): void
  setItem(key: string, data: string): void
  [key: string]: any
  [index: number]: string
}
```

如您所见，它是一个完全同步的存储。 还要注意它仅保存字符串值。 因此，对象被字符串化并存储。

现在看 Local Forage 代表的接口 -

```typescript
export interface LocalForage {
  getItem<T>(key: string): Promise<T>
  setItem<T>(key: string, data: T): Promise<T>
  removeItem(key: string): Promise<void>
  clear(): Promise<void>
  length(): Promise<number>
  key(keyIndex: number): Promise<string>
  _config?: {
    name: string
  }
}
```

你可以在这注意到 2 个不同之处-

1. 所有函数都使用 Promises 来进行异步（因为 WebSQL 和 IndexedDB 是异步的）
2. 它保存的可以是对象（不仅仅是字符串）

当使用异步（基于 Promise）存储时，您的状态将**不会**立即从 localForage 恢复到 vuex。它将进入事件循环，并在 JS 线程为空时完成。这可以调用几秒钟的延迟。

[参考这个来处理](https://github.com/championswimmer/vuex-persist/issues/15)

## 参考

- [vuex-persist](https://github.com/championswimmer/vuex-persist)
- [html5plus](http://www.html5plus.org/doc/h5p.html)