Vuex plugins插件机制以及持久化存储插件源码解读
Vuex Centralized State Management for Vue.js.
vuex-persistedstate Persist and rehydrate your Vuex state between page reloads.
Vuex插件注册源码
- 注册订阅事件存储到_subscribers列表中
- 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变化前后的数据
- 存储打印日志事件到_subscribers列表中
- 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作者跑路没在维护了,不过实现还是很赞的
- 存储setState事件到_subscribers列表中
- commit事件,执行setState更新本地化存储
- 页面重载的时候通过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函数再变更数据的数据流过程,执行插件内定义的订阅事件监听数据变化
- 插件都做成嵌套函数的方式,方便我们自定义传参以及自定义多个插件