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实例的
js
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
源码
js
import { inject } from 'vue'
export const storeKey = 'store'
export function useStore (key = null) {
return inject(key !== null ? key : storeKey)
}
commit
源码
typescript
commit (_type, _payload, _options) {
const { type, payload, options } = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
// 查找该类型对应的 mutations
const 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一致
typescript
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实例
typescript
pinia.use(plugin) {
if (!this._a && !isVue2) {
toBeInstalled.push(plugin)
} else {
_p.push(plugin)
}
return this
}
typescript
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 type
useStore._pinia = pinia
}
}
}
typescript
createSetupStore () {
pinia._p.forEach((extender) => {
assign(
store,
scope.run(() =>
extender({
store: store as Store,
app: pinia._a,
pinia,
options: optionsForPlugin,
})
)!
)
})
}