1、为什么需要状态管理?
在Vue中最重要就是 数据驱动 和 组件化,当 多个组件/多层组件嵌套 需要共享同一个状态或需要对同一数据进行变更时,就会变得复杂,不易维护,所以需要进行状态管理,负责组件间的通信,方便维护代码。
常用的状态管理库:Vuex、Pinia。
使用 Vuex 的好处:
-
能够在 vuex 中集中管理共享的数据,易于开发和后期维护;
-
能够高效地实现组件之间的数据共享,提高开发效率;
-
在 vuex 中的数据都是响应式的;
2、Vuex的介绍
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
1、Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
2、不能直接改变 store 中的状态。改变 store 中的状态的唯一方式是显式地提交 (commit) mutation。这样可以方便地跟踪每一个状态的变化。
3、Vuex的原理
3.1、组件内部如何访问 store 实例
var Store = function Store (options) {
// XXX
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
// XXX
}
function install (_Vue) {
if (Vue && _Vue === Vue) {
{
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
);
}
return
}
Vue = _Vue;
applyMixin(Vue);
}
function applyMixin (Vue) {
var version = Number(Vue.version.split('.')[0]);
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit });
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
var _init = Vue.prototype._init;
Vue.prototype._init = function (options) {
if ( options === void 0 ) options = {};
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit;
_init.call(this, options);
};
}
/**
* Vuex init hook, injected into each instances init hooks list.
*/
function vuexInit () {
var options = this.$options;
// store injection
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store;
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store;
}
}
}
3.2、vuex的数据响应式
// 初始化负责响应性的store vm
function resetStoreVM (store, state, hot) {
var oldVm = store._vm;
// bind store public getters
store.getters = {};
// reset local getters cache
store._makeLocalGettersCache = Object.create(null);
var wrappedGetters = store._wrappedGetters;
var computed = {};
forEachValue(wrappedGetters, function (fn, key) {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldVm.
// using partial to return function with only arguments preserved in closure environment.
computed[key] = partial(fn, store);
Object.defineProperty(store.getters, key, {
get: function () { return store._vm[key]; },
enumerable: true // for local getters
});
});
// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
var silent = Vue.config.silent;
Vue.config.silent = true;
store._vm = new Vue({
data: {
$$state: state
},
computed: computed
});
Vue.config.silent = silent;
// enable strict mode for new vm
if (store.strict) {
enableStrictMode(store);
}
if (oldVm) {
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
store._withCommit(function () {
oldVm._data.$$state = null;
});
}
Vue.nextTick(function () { return oldVm.$destroy(); });
}
}
4、Pinia
pinia支持vue2跟vue3是因为使用了vue-demi提供的能力
4.1、pinia 原理
-
pinia中可以定义多个store,每个store都是一个reactive对象
-
pinia的实现借助了scopeEffect
-
全局注册一个rootPinia,通过provide提供pinia
-
每个store使用都必须在setup中,因为这里才能inject到pinia
4.2、pinia的state的实现
export const symbolPinia = Symbol("rootPinia");
import { App, effectScope, markRaw, Plugin, ref, EffectScope, Ref } from "vue";
import { symbolPinia } from "./rootStore";
export const createPinia = () => {
// 作用域scope 独立空间
const scope = effectScope(true);
// run方法发返回值就是这个fn的返回结果
const state = scope.run(() => ref({}));
// 将一个对象标记为不可被转为代理。返回该对象本身。
const pinia = markRaw({
install(app: App) {
// pinia希望能被共享出去
// 将pinia实例暴露到app上,所有的组件都可以通过inject注入进去
app.provide(symbolPinia, pinia);
// 可以在模板访问 直接通过 $pinia访问根pinia
app.config.globalProperties.$pinia = pinia;
// pinia也记录一下app 方便后续使用
pinia._a = app;
},
// 所有的state
state,
_e: scope, // 管理整个应用的scope
// 所有的store
_s: new Map(),
} as Plugin & IRootPinia);
return pinia;
};
export interface IRootPinia {
[key: symbol]: symbol;
_a: App;
state: Ref<any>;
_e: EffectScope;
_s: Map<string, any>;
}
4.3、defineStore
import {
getCurrentInstance,
inject,
effectScope,
EffectScope,
reactive,
} from "vue";
import { IRootPinia } from "./createPinia";
import { symbolPinia } from "./rootStore";
export function defineStore(options: IPiniaStoreOptions): any;
export function defineStore(
id: string,
options: Pick<IPiniaStoreOptions, "actions" | "getters" | "state">
): any;
export function defineStore(id: string, setup: () => any): any;
export function defineStore(idOrOptions: any, storeSetup?: any) {
let id: string, options: any;
if (typeof idOrOptions === "string") {
id = idOrOptions;
options = storeSetup;
} else {
// 这里就是一个参数的形式 id参数定义在对象内
options = idOrOptions;
id = idOrOptions.id;
}
// 注册一个store
function useStore() {
// 必须在setup中使用
const currentInstance = getCurrentInstance();
if (!currentInstance) throw new Error("pinia 需要在setup函数中使用");
// 注入 pinia
const pinia = inject<IRootPinia>(symbolPinia)!;
// 还没注册
if (!pinia._s.has(id)) {
// counter:state:{count:0}
createOptionsStore(id, options, pinia);
}
// 获取store
const store = pinia._s.get(id);
return store;
}
return useStore;
}
const createOptionsStore = (
id: string,
options: Pick<IPiniaStoreOptions, "actions" | "getters" | "state">,
pinia: IRootPinia
) => {
const { state, getters, actions } = options;
// store单独的scope
let scope: EffectScope;
const setup = () => {
// 缓存 state
if (pinia.state.value[id]) {
console.warn(`${id} store 已经存在!`);
}
const localState = (pinia.state.value[id] = state ? state() : {});
return localState;
};
// scope可以停止所有的store 每个store也可以停止自己的
const setupStore = pinia._e.run(() => {
scope = effectScope();
return scope.run(() => setup());
});
// 一个store 就是一个reactive对象
const store = reactive({});
Object.assign(store, setupStore);
// 向pinia中放入store
pinia._s.set(id, store);
console.log(pinia)
};
export interface IPiniaStoreOptions {
id?: string;
state?: () => any;
getters?: any;
actions?: any;
}
4.4、actions 和 getters
const createOptionsStore = (
id: string,
options: Pick<IPiniaStoreOptions, "actions" | "getters" | "state">,
pinia: IRootPinia
) => {
const { state, getters = {}, actions } = options;
// store单独的scope
let scope: EffectScope;
const setup = () => {
// 缓存 state
if (pinia.state.value[id]) {
console.warn(`${id} store 已经存在!`);
}
const localState = (pinia.state.value[id] = state ? state() : {});
return Object.assign(
localState,
actions,
Object.keys(getters).reduce(
(computedGetter: { [key: string]: ComputedRef<any> }, name) => {
// 计算属性可缓存
computedGetter[name] = computed(() => {
// 我们需要获取当前的store是谁
return Reflect.apply(getters[name], store, [store]);
});
return computedGetter;
},
{}
)
);
};
// scope可以停止所有的store 每个store也可以停止自己的
const setupStore = pinia._e.run(() => {
scope = effectScope();
return scope.run(() => setup());
});
// 一个store 就是一个reactive对象
const store = reactive({});
// 处理action的this问题
for (const key in setupStore) {
const prop = setupStore[key];
if (typeof prop === "function") {
// 扩展action
setupStore[key] = wrapAction(key, prop, store);
}
}
Object.assign(store, setupStore);
// 向pinia中放入store
pinia._s.set(id, store);
setTimeout(() => {
console.log(pinia);
}, 2000);
};
const wrapAction = (key: string, action: any, store: any) => {
return (...args: Parameters<typeof action>) => {
// 触发action之前 可以触发一些额外的逻辑
const res = Reflect.apply(action, store, args);
// 返回值也可以做处理
return res;
};
};
4.5、setupStore的原理
function useStore() {
// 必须在setup中使用
const currentInstance = getCurrentInstance();
if (!currentInstance) throw new Error("pinia 需要在setup函数中使用");
// 注入 pinia
const pinia = inject<IRootPinia>(symbolPinia)!;
// 还没注册
if (!pinia._s.has(id)) {
if (isSetupStore) {
// 创建setupStore
createSetupStore(id, storeSetup, pinia);
} else {
// counter:state:{count:0}
createOptionsStore(id, options, pinia);
}
}
// 获取store
const store = pinia._s.get(id);
return store;
}
const createSetupStore = (id: string, setup: () => any, pinia: IRootPinia) => {
// 一个store 就是一个reactive对象
const store = reactive({});
// store单独的scope
let scope: EffectScope;
// scope可以停止所有的store 每个store也可以停止自己的
const setupStore = pinia._e.run(() => {
scope = effectScope();
return scope.run(() => setup());
});
// 处理action的this问题
for (const key in setupStore) {
const prop = setupStore[key];
if (typeof prop === "function") {
// 扩展action
setupStore[key] = wrapAction(key, prop, store);
}
}
Object.assign(store, setupStore);
// 向pinia中放入store
pinia._s.set(id, store);
return store;
};
const createOptionsStore = (
id: string,
options: Pick<IPiniaStoreOptions, "actions" | "getters" | "state">,
pinia: IRootPinia
) => {
const { state, getters = {}, actions } = options;
const setup = () => {
// 缓存 state
if (pinia.state.value[id]) {
console.warn(`${id} store 已经存在!`);
}
const localState = (pinia.state.value[id] = state ? state() : {});
return Object.assign(
localState,
actions,
Object.keys(getters).reduce(
(computedGetter: { [key: string]: ComputedRef<any> }, name) => {
// 计算属性可缓存
computedGetter[name] = computed(() => {
// 我们需要获取当前的store是谁
return Reflect.apply(getters[name], store, [store]);
});
return computedGetter;
},
{}
)
);
};
const store = createSetupStore(id, setup, pinia);
};