1、为什么要用状态管理库?
多组件的状态共享问题: 当多个组件需要访问和修改相同的数据时,我们需要在组件之间传递 props或者使用事件总线。当,应用就会变得难以维护和调试。
多组件状态同步问题: 当一个组件修改了状态,其他组件可能无法立即得知该变化。
状态变更的追踪问题: 无法追踪到状态的变化是由何处引起的,使得调试和维护变得困难。
2、Vuex
2.1、核心概念
2.1.1、State:用于存储应用程序的状态数据
当你需要在多个组件之间共享数据时,可以将这些数据放入state
中。
例如,保存用户登录状态、购物车中的商品列表等。
可以通过在组件中使用store.state.xxx
或计算属性来获取状态数据。
2.1.2、Mutation:用于修改状态的方法,必须是同步函数。
什么时候用? 当你需要修改状态时。让所有的状态变更都经过 mutation
可以保证状态的变更是可追踪的。
通常,一个 mutation
对应一个状态变更操作。例如,修改用户登录状态、添加商品到购物车等。
2.1.3、Action:用于处理异步逻辑或提交多个 mutation。
什么时候用? 当你需要处理异步操作(例如发起网络请求)或需要在一个 action
中提交多个 mutation
时。
Action
可以包含任意的异步操作,并可以通过提交 mutation
来修改状态。
例如,获取用户信息的异步请求、添加多个商品到购物车等。
2.1.4、Getter:用于从状态中获取派生数据的方法。
什么时候用? 当你需要根据状态state.xxx
计算出一些数据时。
Getter 可以将一些复杂的数据计算逻辑封装起来,并在组件中使用 store.getters
来获取计算后的数据。
例如,基于购物车商品列表计算购物车总价、根据用户权限判断是否具有管理员角色等。
2.2、原理(v4.0.2)
2.2.1、vuex如何挂载到vue实例的
install (app, injectKey) {// 使用`vue.provide()`将`vuex`提供给整个应用app.provide(injectKey || storeKey, this)// 将vuex实例赋值给vue.$store;// 在项目的非setup中可以使用this.$store.state.xxx取值就是这样来的app.config.globalProperties.$store = this
}
2.2.2、useStore
源码
import { inject } from 'vue'export const storeKey = 'store'export function useStore (key = null) {return inject(key !== null ? key : storeKey)
}
commit
源码
commit (_type, _payload, _options) {const { type, payload, options } = unifyObjectStyle(_type, _payload, _options)const mutation = { type, payload }// 查找该类型对应的 mutationsconst entry = this._mutations[type]if (!entry) {return}// 执行mutations对应的处理函数this._withCommit(() => {entry.forEach(function commitIterator (handler) {handler(payload)})})// 通知订阅者this._subscribers.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe.forEach(sub => sub(mutation, this.state))}
3、Pinia
相比vuex的优势:
- 可通过devtools追踪数据变化,无需通过
commit
提交Mutation
- 支持TS,提供代码自动补全,源码为TS编写;vuex是用JS编写的,vuex要支持TS需要安装插件
- pinia更轻,大小只有 1kb 左右
改变状态的方法
- 直接修改变量
- 调用action
- 调用patch
3.1原理
install 原理与vuex一致
let toBeInstalled: PiniaPlugin[] = []install(app: App) {if (!isVue2) {pinia._a = app// 暴露pinia,组件通过inject注入pinia实例app.provide(piniaSymbol, pinia)// 模版中可通过$pinia访问app.config.globalProperties.$pinia = pinia// 将pinia的plugin 存到插件列表toBeInstalled.forEach((plugin) => _p.push(plugin))toBeInstalled = []}},
pinia的plugin实现原理
1、在调用vue.use(pinia)
之前注入插件的情况,会将plugin存放到toBeInstalled列表,
2、调用vue.use(pinia)
之后,会将toBeInstalled的插件存到pinia实例的_p中
3、调用useStore时将plugin注入每个Store实例
pinia.use(plugin) {if (!this._a && !isVue2) {toBeInstalled.push(plugin)} else {_p.push(plugin)}return this}
useStore(pinia) {if (!pinia._s.has(id)) {// creating the store registers it in `pinia._s`if (isSetupStore) {createSetupStore(id, setup, options, pinia)} else {createOptionsStore(id, options as any, pinia)}/* istanbul ignore else */if (__DEV__) {// @ts-expect-error: not the right inferred typeuseStore._pinia = pinia}}
}
createSetupStore () {pinia._p.forEach((extender) => {assign(store,scope.run(() =>extender({store: store as Store,app: pinia._a,pinia,options: optionsForPlugin,}))!)})
}