Jinuss's blog Jinuss's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

前端可视化
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • React

  • Vue

  • JavaScript文章

  • 学习笔记

  • openlayers

  • threejs

  • MapboxGL

  • 工具

  • 源码合集

    • Pinia源码浅析
    • 《Vue3源码》笔记
    • vue-router源码浅析
    • pinia-plugin-persistedstate源码浅析
      • 概述
      • pinia-plugin-persistedstate的使用
      • 源码剖析
        • pinia.use 方法
        • pinia使用插件
        • pinia-plugin-persistedstate
        • createPersistedState
        • 辅助函数或方法
        • hydrateStore 方法
        • persistState 方法
        • normalizeOption
        • parsePersistence
      • 总结
    • 《Openlayers源码》笔记
    • 《Leaflet源码》笔记
  • 前端
  • 源码合集
东流
2024-09-10
目录

pinia-plugin-persistedstate源码浅析

# 概述

Pinia是vue3的官方推荐用于数据共享的库,但是Pinia🍍中的数据是存在于浏览器的内存中,当浏览器刷新后,这些数据就会消失。因此我们需要对数据做持久化存储,这个时候就需要用到pinia-plugin-persistedstate。

pinia-plugin-persistedstate本质上利用浏览器持久化存储地能力,默认使用localStorage。本文将详细介绍pinia-plugin-persistedstate的使用以及源码剖析,以vue3项目为例。

# pinia-plugin-persistedstate的使用

使用pinia-plugin-persistedstate之前需要安装pinia和pinia-plugin-persistedstate。其使用主要分为两部分

  • 在main.js中引用,如示例:
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
1
2
3
  • 在store中使用,pinia的defineStore的第二个参数,提供一个persist属性,配置该属性即可。
export const useCommonStore = defineStore("common", {
  persist: {
    key: "common", // localStorage的建
    storage: localStorage, //指定存储方式,默认为localStorage
    paths: ["menuActiveIndex", "collapse", "state", "currentMenuList"], // 数组中的为state的键,需要持久化的state放在paths中
  },
});
1
2
3
4
5
6
7
  • 其效果如下

# 源码剖析

pinia-plugin-persistedstate是为 pinia量身定制的持久化方案,因此我们需要对pinia有一定的了解,可以参考之前的文章Pinia 源码浅析 (opens new window),PS:该文章会持续更新。本文会只讲解pinia中与pinia-plugin-persistedstate有关的部分。

# pinia.use 方法

由上可知,通过pinia.use去使用pinia-plugin-persistedstate,pinia.use实现如下:

 use(plugin) {
    if (!this._a && !isVue2) {
        toBeInstalled.push(plugin);
    }
    else {
        _p.push(plugin);
    }
    return this;
},
1
2
3
4
5
6
7
8
9

该use方法是在createPinia中定义的,调用use方法,首先会判断_a是否存在以及当前是vue2还是vue3,如果当前_a不存在(即还没有调用app.use(pinia))并且是vue3,则将pinia-plugin-persistedstate插件放到toBeInstalled变量中,否则将插件放大_p中。

放到toBeInstalled的插件会在pinia被vue3实例调用use方法时push到_p中:

toBeInstalled.forEach((plugin) => _p.push(plugin));
1

由此可知,pinia中的插件全部在_p中。

# pinia使用插件

pinia内部使用插件的部分是在createSetupStore中实现的,即defineStore——>useStore——>createSetupStore,其实现如下:

pinia._p.forEach((extender) => {
  /* istanbul ignore else */
  if (USE_DEVTOOLS) {
    //调试部分
    const extensions = scope.run(() =>
      extender({
        store,
        app: pinia._a,
        pinia,
        options: optionsForPlugin,
      })
    );
    Object.keys(extensions || {}).forEach((key) =>
      store._customProperties.add(key)
    );
    assign(store, extensions);
  } else {
    assign(
      store,
      scope.run(() =>
        extender({
          store,
          app: pinia._a,
          pinia,
          options: optionsForPlugin,
        })
      )
    );
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

这段代码就是在 vue 组件中使用store的useStore方法时去遍历pinia安装的插件,调用插件暴露的方法extender,对应pinia-plugin-persistedstate就是该插件暴露的方法createPersistedState,将store、app(即 vue 实例)、pinia、options: optionsForPlugin传给插件

# pinia-plugin-persistedstate

pinia-plugin-persistedstate只暴露了一个接口:createPersistedState,默认暴露createPersistedState()的实例

# createPersistedState

createPersistedState 方法返回的是一个函数,其实现如下:

function createPersistedState(factoryOptions = {}) {
  return (context) => {
    const { auto = false } = factoryOptions;
    const {
      options: { persist = auto },
      store,
      pinia,
    } = context;
    if (!persist) return;
    if (!(store.$id in pinia.state.value)) {
      const original_store = pinia._s.get(store.$id.replace("__hot:", ""));
      if (original_store)
        Promise.resolve().then(() => original_store.$persist());
      return;
    }
    const persistences = (
      Array.isArray(persist)
        ? persist.map((p) => normalizeOptions(p, factoryOptions))
        : [normalizeOptions(persist, factoryOptions)]
    )
      .map(parsePersistence(factoryOptions, store))
      .filter(Boolean);
    store.$persist = () => {
      persistences.forEach((persistence) => {
        persistState(store.$state, persistence);
      });
    };
    store.$hydrate = ({ runHooks = true } = {}) => {
      persistences.forEach((persistence) => {
        const { beforeRestore, afterRestore } = persistence;
        if (runHooks) beforeRestore == null ? void 0 : beforeRestore(context);
        hydrateStore(store, persistence);
        if (runHooks) afterRestore == null ? void 0 : afterRestore(context);
      });
    };
    persistences.forEach((persistence) => {
      const { beforeRestore, afterRestore } = persistence;
      beforeRestore == null ? void 0 : beforeRestore(context);
      hydrateStore(store, persistence);
      afterRestore == null ? void 0 : afterRestore(context);
      store.$subscribe(
        (_mutation, state) => {
          persistState(state, persistence);
        },
        {
          detached: true,
        }
      );
    });
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

接受的参数context就是pinia中传过来的store、app、pinia、options。

若使用defineStore时,没有配置persist属性,则context中options的persist取factoryOptions的auto的值,默认为false,此时,函数就终止,return。

然后插件会检查store是否存在于pinia.state.value中,pinia.state.value是一个包含所有当前存储实例的对象。若不存在,则处理下热加载的前缀并从pinia._s中获取原始的存储实例,如果原始实例存在,则用promise将其$persist方法放在微任务对列中去执行。

若store存在,则判断persist是数组还是对象。若persist是数组,则对它遍历,执行normalizeOptions方法;否则直接调用normalizeOptions,然后包装成数组,遍历,执行parsePersistence方法。

然后插件定义了两个方法store.$persist和store.$hydrate。$persist就是遍历persistences,去执行persistState方法;$hydrate水合,就是用来从存储中恢复数据到store中,但是在pinia和pinia-plugin-persistedstate中未看到相关调用。

最后插件遍历了persistences,在调用hydrateStore的前后会判断beforeRestore和afterRestore是否定义了,若存在,则直接调用,最后还调用了store.$subscribe方法进行监听state的改变,若state发生了改变,则会执行回调函数persistState,其过程如下:

const { beforeRestore, afterRestore } = persistence;
beforeRestore == null ? void 0 : beforeRestore(context);
hydrateStore(store, persistence);
afterRestore == null ? void 0 : afterRestore(context);
store.$subscribe(
  (_mutation, state) => {
    persistState(state, persistence);
  },
  {
    detached: true,
  }
);
1
2
3
4
5
6
7
8
9
10
11
12

# 辅助函数或方法

# hydrateStore 方法

hydrateStore方法就是从storage中去取数据,然后进行反序列化,调用store.$patch进行更新state的数据,其实现如下:

function hydrateStore(store, { storage, serializer, key, debug }) {
  try {
    const fromStorage = storage == null ? void 0 : storage.getItem(key);
    if (fromStorage)
      store.$patch(
        serializer == null ? void 0 : serializer.deserialize(fromStorage)
      );
  } catch (e) {
    if (debug) console.error("[pinia-plugin-persistedstate]", e);
  }
}
1
2
3
4
5
6
7
8
9
10
11
# persistState 方法

persistState方法就是监听state的变化,将新数据进行序列化后存储到storage中,其实现如下:

function persistState(state, { storage, serializer, key, paths, debug }) {
  try {
    const toStore = Array.isArray(paths) ? pick(state, paths) : state;
    storage.setItem(key, serializer.serialize(toStore));
  } catch (e) {
    if (debug) console.error("[pinia-plugin-persistedstate]", e);
  }
}
1
2
3
4
5
6
7
8

pick方法就是通过store中定义的paths数组去获取state中对应的值。

# normalizeOption

normalizeOption就是通过Proxy创建了一个代理对象,在访问options时,若options不存在该属性,则从factoryOptions中获取。

其实现如下:

function normalizeOptions(options, factoryOptions) {
  options = isObject(options) ? options : /* @__PURE__ */ Object.create(null);
  return new Proxy(options, {
    get(target, key, receiver) {
      if (key === "key") return Reflect.get(target, key, receiver);
      return (
        Reflect.get(target, key, receiver) ||
        Reflect.get(factoryOptions, key, receiver)
      );
    },
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
# parsePersistence

parsePersistence函数就是用来解析store中定义的persist,其主要实现如下:

const {
  storage = localStorage, //默认存储
  beforeRestore = void 0,
  afterRestore = void 0,
  serializer = {
    serialize: JSON.stringify,
    deserialize: JSON.parse,
  },
  key = store.$id,
  paths = null,
  debug = false,
} = o;
return {
  storage,
  beforeRestore,
  afterRestore,
  serializer,
  key: ((_a = factoryOptions.key) != null ? _a : (k) => k)(
    typeof key == "string" ? key : key(store.$id)
  ),
  paths,
  debug,
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

从上述代码可以看出,如果key值未定义,会采用默认的store.$id,若key是字符串,则直接采用;否则执行key(store.$id);另外默认的序列化和反序列化的函数serializer是JSON.stringify和JSON.parse。这里还提供了两个钩子函数beforeRestore和afterRestore。

# 总结

pinia-plugin-persistedstate插件短小而精悍,利用的还是localStorage和pinia的能力进行数据的监听、存储和恢复。

编辑 (opens new window)
上次更新: 2025/04/09, 10:15:29
vue-router源码浅析
《Openlayers源码》笔记

← vue-router源码浅析 《Openlayers源码》笔记→

最近更新
01
GeoJSON
05-08
02
Circle
04-15
03
CircleMarker
04-15
更多文章>
Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式