Skip to content

上次在写前端状态持久化的那篇文章中说到了有机会就会讲解下 vuex-persistedstate 这个持久化插件,毕竟埋了坑不填还是不好 😏,下面我会讲 vuex-persistedstate 源码比较重要的一些代码。下面先上源码:

源码

typescript
import { Store, MutationPayload } from "vuex";
import merge from "deepmerge";
import * as shvl from "shvl";

interface Storage {
  getItem: (key: string) => any;
  setItem: (key: string, value: any) => void;
  removeItem: (key: string) => void;
}

interface Options {
  key?: string;
  paths?: string[];
  reducer?: (state: any, paths: string[]) => object;
  subscriber?: (
    store: typeof Store
  ) => (handler: (mutation: any, state: any) => void) => void;
  storage?: Storage;
  getState?: (key: string, storage: Storage) => any;
  setState?: (key: string, state: typeof Store, storage: Storage) => void;
  filter?: (mutation: MutationPayload) => boolean;
  arrayMerger?: (state: any, saved: any) => any;
  rehydrated?: (store: typeof Store) => void;
  fetchBeforeUse?: boolean;
  overwrite?: boolean;
  assertStorage?: (storage: Storage) => void | Error;
}

export default function(options?: Options) {
  options = options || {};

  const storage = options.storage || (window && window.localStorage);
  const key = options.key || "vuex";

  function getState(key, storage) {
    let value;

    try {
      return (value = storage.getItem(key)) && typeof value !== "undefined"
        ? JSON.parse(value)
        : undefined;
    } catch (err) {}

    return undefined;
  }

  function filter() {
    return true;
  }

  function setState(key, state, storage) {
    return storage.setItem(key, JSON.stringify(state));
  }

  function reducer(state, paths) {
    return Array.isArray(paths)
      ? paths.reduce(function(substate, path) {
          return shvl.set(substate, path, shvl.get(state, path));
        }, {})
      : state;
  }

  function subscriber(store) {
    return function(handler) {
      return store.subscribe(handler);
    };
  }

  const assertStorage =
    options.assertStorage ||
    (() => {
      storage.setItem("@@", 1);
      storage.removeItem("@@");
    });

  assertStorage(storage);

  const fetchSavedState = () => (options.getState || getState)(key, storage);

  let savedState;

  if (options.fetchBeforeUse) {
    savedState = fetchSavedState();
  }

  return function(store) {
    if (!options.fetchBeforeUse) {
      savedState = fetchSavedState();
    }

    if (typeof savedState === "object" && savedState !== null) {
      store.replaceState(
        options.overwrite
          ? savedState
          : merge(store.state, savedState, {
              arrayMerge:
                options.arrayMerger ||
                function(store, saved) {
                  return saved;
                },
              clone: false,
            })
      );
      (options.rehydrated || function() {})(store);
    }

    (options.subscriber || subscriber)(store)(function(mutation, state) {
      if ((options.filter || filter)(mutation)) {
        (options.setState || setState)(
          key,
          (options.reducer || reducer)(state, options.paths),
          storage
        );
      }
    });
  };
}

能看懂的可以右上角了,看不懂或者有点懵但是又有些能看懂的同学们看我们慢慢来解剖:

结合源码讲述接口参数字段用途

typescript
// 定义要操作本地存储的方法
interface Storage {
  getItem: (key: string) => any;
  setItem: (key: string, value: any) => void;
  removeItem: (key: string) => void;
}

// 设置的参数
interface Options {
  key?: string;
  paths?: string[];
  reducer?: (state: any, paths: string[]) => object;
  subscriber?: (
    store: typeof Store
  ) => (handler: (mutation: any, state: any) => void) => void;
  storage?: Storage;
  getState?: (key: string, storage: Storage) => any;
  setState?: (key: string, state: typeof Store, storage: Storage) => void;
  filter?: (mutation: MutationPayload) => boolean;
  arrayMerger?: (state: any, saved: any) => any;
  rehydrated?: (store: typeof Store) => void;
  fetchBeforeUse?: boolean;
  overwrite?: boolean;
  assertStorage?: (storage: Storage) => void | Error;
}

我们来解析 Options 接口中比较重要的参数字段:

key

key 就是你要存到本地缓存的名字, 默认是 vuex,比如你不选择 Storage 的类型,那么 vuex-persistedstate 就会默认存在 localstorage 里面,等于你的 vuex 的值就会被 localstorage.setItem('vuex', data)存进去。

paths reducer

这两个字段是配合来使用的,paths 是数组,reducer 是当然可以只传 paths 字段或者只传 reducer,指定只保存的值,可以直接看源码的其中一段就能够明白这两个字段的作用:

typescript
function reducer(state, paths) {
  return Array.isArray(paths)
    ? paths.reduce(function(substate, path) {
        return shvl.set(substate, path, shvl.get(state, path));
      }, {})
    : state;
}

subscriber storage getState setState filter

这四个字段是需要一起来讲的,storage 定义要操作本地存储的方法,目前支持三种:localstorage、sessionstorage、cookie。getState 和 setState 分别定义获取和改变本地储存的指的方法。subscriber 设置 vuex 的 mutaition 订阅的函数,默认是一个 handle => {},filter 则是传入一个函数,这个函数的参数是 vuex 的 mutation,这个参数不触发 setState,这两个参数也是相互配合的,从源码可以看出:

typescript
// subscriber的定义
function subscriber(store) {
  return function(handler) {
    return store.subscribe(handler);
  };
}

// filter的定义
function filter() {
  return true;
}

(options.subscriber || subscriber)(store)(function(mutation, state) {
  if ((options.filter || filter)(mutation)) {
    (options.setState || setState)(
      key,
      (options.reducer || reducer)(state, options.paths),
      storage
    );
  }
});

从上面的代码可以看出subscriber第一次运行之后是返回一个函数,这个函数的也被定义过:

typescript
subscriber?: (
    store: typeof Store
  ) => (handler: (mutation: any, state: any) => void) => void;

也就是需要是mutation,跟filter的参数类型是一样的,所以只要不定义subscriber和filter那么就是默认会执行setState去保存vuex的指,如果传入了vuex的mutation(首先你要保证自己写的vuex里面有这个mutation)而且函数返回的是false的话就不会触发保存在定义的storage上。

总结

其实这个这库很适合typescript的新手去学习以及模仿,逻辑不会太简单也不会太难,还能巩固下typescript的知识。这个坑算是填完了