Mercer-Lee的空间

vuePress-theme-reco Mercer-Lee的空间    2018 - 2024
Mercer-Lee的空间 Mercer-Lee的空间

Choose mode

  • dark
  • auto
  • light
TimeLine
分类
  • 数据结构和算法
  • 后端
  • 运维
  • 前端
  • 工具
  • 语言
标签
我的GitHub (opens new window)
author-avatar

Mercer-Lee的空间

27

文章

29

标签

TimeLine
分类
  • 数据结构和算法
  • 后端
  • 运维
  • 前端
  • 工具
  • 语言
标签
我的GitHub (opens new window)
  • vuex-persistedstate源码解析

    • 源码
      • 结合源码讲述接口参数字段用途
        • key
        • paths reducer
        • subscriber storage getState setState filter
      • 总结

      vuex-persistedstate源码解析

      vuePress-theme-reco Mercer-Lee的空间    2018 - 2024

      vuex-persistedstate源码解析


      Mercer-Lee的空间 2020-02-11 JS Vue TS

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

      # 源码

      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
              );
            }
          });
        };
      }
      

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

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

      // 定义要操作本地存储的方法
      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,指定只保存的值,可以直接看源码的其中一段就能够明白这两个字段的作用:

      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,这两个参数也是相互配合的,从源码可以看出:

      // 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第一次运行之后是返回一个函数,这个函数的也被定义过:

      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的知识。这个坑算是填完了