Vuex plugins插件机制以及持久化存储插件源码解读

310 阅读3分钟

Vuex plugins插件机制以及持久化存储插件源码解读

Vuex Centralized State Management for Vue.js.

vuex-persistedstate Persist and rehydrate your Vuex state between page reloads.

Vuex插件注册源码

  1. 注册订阅事件存储到_subscribers列表中
  2. commit事件触发执行_subscribers列表中自定义事件
// store.js
export class Store {
  constructor (options) {
    ...
    const { plugins } = options
    this._subscribers = [] // 订阅事件数组
    plugins.forEach(plugin => plugin(this)) // 实例化后调用插件函数,把store实例传入插件中
  }
  // 注册订阅事件
 subscribe(fn) {
  this._subscribers.push(fn)
 }
 
 commit (_type, _payload, _options) {
  ...
  // 触发订阅事件
  this._subscribers.forEach(sub => sub(mutation, this.state))
 }
}

内置logger插件

vuex调试使用 console.groupCollapsed 打印state变化前后的数据

  1. 存储打印日志事件到_subscribers列表中
  2. commit事件,执行_subscribers列表中的打印日志
// logger.js
export default function createLogger ({
  collapsed = false,
  filter = (mutation, stateBefore, stateAfter) => true,
  transformer = state => state,
  mutationTransformer = mut => mut,
  logger = console
} = {}) {
  return store => {
    let prevState = deepCopy(store.state) // 深拷贝变动前的数据
    // mutations里面的函数每次执行都会触发到该回调函数进行打印日志
    store.subscribe((mutation, state) => { 
      const nextState = deepCopy(state)
      const formattedMutation = mutationTransformer(mutation)
      logger.groupCollapsed(`${mutation.type}`)
      logger.log('%c prev state', 'color: #9E9E9E; font-weight: bold', transformer(prevState))
      logger.log('%c mutation', 'color: #03A9F4; font-weight: bold', formattedMutation)
      logger.log('%c next state', 'color: #4CAF50; font-weight: bold', transformer(nextState))
      logger.groupEnd()
      prevState = nextState
    })
  }
}

vuex-persistedstate插件

  • 在页面重新加载的时候读取本地存储好的数据状态
  • vuex-persistedstate作者跑路没在维护了,不过实现还是很赞的
  1. 存储setState事件到_subscribers列表中
  2. commit事件,执行setState更新本地化存储
  3. 页面重载的时候通过store的replaceState更新本地化存值到state中
// vuex-persistedstate
import { Store, MutationPayload } from "vuex";
import merge from "deepmerge";
import * as shvl from "shvl"; // shvl 获取设置深层级下对象的数据

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

interface Options<State> {
  key?: string; // 存储名
  paths?: string[]; // 存储模块
  reducer?: (state: State, paths: string[]) => object;
  subscriber?: (
    store: Store<State>
    ) => (handler: (mutation: any, state: State) => void) => void; // 自定义订阅事件,涉及存值
  storage?: Storage; // 自定义存储方式
  getState?: (key: string, storage: Storage) => any; // 自定义存储get方法
  setState?: (key: string, state: any, storage: Storage) => void; // 自定义存储set方法
  filter?: (mutation: MutationPayload) => boolean; // 过滤 mutation事件进行存储
  arrayMerger?: (state: any[], saved: any[]) => any; // 选择覆盖后的 deepmerge的合并方式
  rehydrated?: (store: Store<State>) => void; // replaceState后的回调函数
  fetchBeforeUse?: boolean; // 是否在插件调用之前获取状态
  overwrite?: boolean; // 是否覆盖
  assertStorage?: (storage: Storage) => void | Error; // 测试存储方法是否可行
}

export default function <State>(
  options?: Options<State>
): (store: Store<State>) => void {
  options = options || {};

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

  function getState(key, storage) {
    const value = storage.getItem(key);
    // JSON.parse 兼容处理
    try {
      return (typeof value === "string")
        ? JSON.parse(value) : (typeof value === "object")
        ? value : undefined;
    } catch (err) {}

    return undefined;
  }

  function filter() {
    return true;
  }

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

 // 用来存储部分模块数据
 //  shvl 获取设置深层级下对象的数据
  function reducer(state, paths) {
    return Array.isArray(paths)
      ? paths.reduce(function (substate, path) {
          return shvl.set(substate, path, shvl.get(state, path));
        }, {})
      : state;
  }

  //  注册vuex订阅事件
  function subscriber(store) {
    return function (handler) {
      return store.subscribe(handler);
    };
  }

  
  try {
    const assertStorage =
    options.assertStorage ||
    (() => {
      storage.setItem("@@", 1);
      storage.removeItem("@@");
    });
    assertStorage(storage);
  } catch (error) {
    throw new Error('assertStorage fail!')
  }  
  

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

  let savedState;

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

  // 核心代码 存取值
  return function (store: Store<State>) {
    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);
    }
    
    // 还是利用vuex订阅事件来监听修改data事件变化进行本地化存储存值
    (options.subscriber || subscriber)(store)(function (mutation, state) {
      if ((options.filter || filter)(mutation)) {
        (options.setState || setState)(
          key,
          (options.reducer || reducer)(state, options.paths),
          storage
        );
      }
    });
  };
}

Usage

import { createStore } from "vuex";
import createPersistedState from "vuex-persistedstate";

const store = createStore({
  // ...
  plugins: [createPersistedState()],
});

总结

  • Vuex插件机制简洁,用vuex-persistedstate插件来做本地化存储确实优雅
  • 这两个插件都利用了Vuex执行commit函数再变更数据的数据流过程,执行插件内定义的订阅事件监听数据变化
  • 插件都做成嵌套函数的方式,方便我们自定义传参以及自定义多个插件