Pinia源码解析【2.0.33】
pinia
是 Vue 的专属状态管理库,并且可以同时支持 Vue 2 和 Vue 3。
在Vue3的项目中,我们都会优先使用Pinia
,所以了解其基本的底层原理,有助于我们在项目中更好的应用。
1,createPinia
在Vue3项目中使用pinia
时,都会从pinia
中引入一个createPinia
方法,然后使用这个方法创建一个pinia
实例。
import { createPinia } from 'pinia'
// 创建pinia
const pinia = createPinia()
接下来我们就从import { createPinia } from 'pinia'
这句代码开始pinia
源码的学习。
首先查看createPinia
的源码:
// packages/pinia/src/createPinia.ts
# 创建pinia方法
export function createPinia(): Pinia {
const scope = effectScope(true)
// 初始化响应式state:默认为一个ref数据【目的是存储:所有store的state的集合】
// RefImpl:{ value: proxy:{} }
const state = scope.run(() => ref({}))
// 插件列表
let _p: Pinia['_p'] = []
// 待安装插件列表
let toBeInstalled: PiniaPlugin[] = []
# 创建pinia实例对象
// 这里用markRaw方法标记了Pinia对象,不需要被转换为响应式数据
const pinia: Pinia = markRaw({
# 定义的Install方法:每个vue插件都需要显示定义,在app.use时调用插件的install方法
install(app: App) {
// 设置为当前活跃的pinia
setActivePinia(pinia)
# 在Vue3应用的情况下:
if (!isVue2) {
// 存储Vue3 app应用实例
pinia._a = app
# app.provide()提供一个值,可以在应用中的所有后代组件中注入使用【重点】
app.provide(piniaSymbol, pinia)
// 设置全局属性$pinia:在vue应用内的每个组件都可以通过this.$pinia访问
app.config.globalProperties.$pinia = pinia
// 注册开发者工具
if (USE_DEVTOOLS) {
registerPiniaDevtools(app, pinia)
}
// 将插件列表添加到_p属性
toBeInstalled.forEach((plugin) => _p.push(plugin))
// 清空待安装插件列表
toBeInstalled = []
}
},
# pinia对外暴露的插件注册方法
use(plugin) {
if (!this._a && !isVue2) {
// vue3: 添加到待安装插件列表:初始化时使用
toBeInstalled.push(plugin)
} else {
// vue2:直接添加到_p属性
_p.push(plugin)
}
return this
},
_p, // 插件列表
_a: null, // vue3 app应用实例
_e: scope,
_s: new Map<string, StoreGeneric>(), # 一个map结构,存储storeId,与store实例
state, // 所有的state的集合:每个store初始化时,都会将处理后state数据挂载到Pinia.state.value[$id]下面
})
// 注册开发者工具插件
if (USE_DEVTOOLS && typeof Proxy !== 'undefined') {
pinia.use(devtoolsPlugin)
}
# 返回pinia实例对象
return pinia
}
可以看出createPinia
方法的内容比较简单,主要就是创建了一个Pinia实例并返回。pinia
实例里面定义了两个方法和几个属性,
我们首先看两个方法:
- install方法:将pinia注册到vue实例。
- use方法:将其他插件注册到pinia实例。
const app = createApp(App)
const pinia = createPinia()
// 注册pinia:会在内部调用pinia的install方法
app.use(pinia)
install
方法内容并不多,最主要就是将pinia实例注入到全局,让vue应用下的每个组件实例都可以使用。
app.provide(piniaSymbol, pinia)
然后重点介绍两个属性:
pinia._s
:可以看出这个属性是一个map结构,它的作用就是存储我们在项目中定义的store实例,它是以键值对的方式进行注册pinia._s.set($id, store)
,它的作用是在我们多次引用useStore时,不会重复新建Store,而是直接返回已存在的对象。pinia.state
:这个属性被初始化为一个ref数据,它的作用是存储每个store实例中的state数据,每个store在创建时,都会将state进行处理后挂载到Pinia.state.value[$id]
下面,具体的行为可以在后面createXXXStore中查看。
2,defineStore
下面我们开始阅读defineStore
的源码:
// packages/pinia/src/createPinia.ts
/**
* 两个参数:
* 参数1,store的唯一标识符;
* 参数2,可接收两类值,Setup函数或者Options对象;【相当于使用组合式和选项式,随便用哪种都行】
*/
# 定义store方法【重点】
export function defineStore(
idOrOptions: any,
setup?: any,
setupOptions?: any
): StoreDefinition {
// 声明两个变量
let id: string
let options
# 根据第二参数的类型来:判断是否使用的setupStore模式
const isSetupStore = typeof setup === 'function'
// 确定两个变量【赋值】
if (typeof idOrOptions === 'string') {
// 默认的情况:defineStore('counter', {state:()=>{},....})
id = idOrOptions
// 这里如果为setupStore,setupOptions就是undefined,即options为undef
options = isSetupStore ? setupOptions : setup
} else {
// defineStore({'counter', state:()=>{},....})
options = idOrOptions
id = idOrOptions.id
}
# 定义了一个useStore方法【重点】
// 即我们调用时的函数:const useMainStore = defineStore('main')
function useStore(pinia?, hot?) { ... }
// 设置store唯一标识符
useStore.$id = id
# 返回useStore
return useStore
}
defineStore
方法比较重要,是我们在定义Store时要经常使用的,defineStore
方法只有两个参数:
- 参数1:store实例的id,必须保证独一无二。
- 参数2:可接收两类值,Setup函数或者Options对象。
这里defineStore
方法里面大部分代码都是useStore
函数的内容,所以我们先把它折叠起来,后面再分析。
注意:我们在阅读开源项目源码的时候,一定要学会内容拆分,因为有的函数源码量非常大,有的能达到几百行甚至上千行【比如Vue3源码baseCreateRenderer基础渲染器函数有两千多行代码】,我们要拆分其内容,降低复杂度,然后才能更好的解析其主要逻辑。在熟悉其主要逻辑之后,我们可以进行源码调试,通过step步入每个函数内容,验证之前的逻辑分析。
当我们把useStore
函数折叠后,defineStore
方法的内容就很简洁明了了,首先处理传入的参数,然后定义了一个useStore
方法并且返回,这里的重点是定义的useStore
方法:
// 在项目中
const useMainStore = defineStore('main', {...})
这里我们在项目中声明的useMainStore
函数内容:就是调用defineStore
后返回的useStore
方法。
下面我们继续解析useStore
源码:
useStore
# Store方法
function useStore(pinia?, hot?) {
// 获取当前组件实例
const currentInstance = getCurrentInstance()
# 将pinia注入到当前组件实例并使用
pinia = currentInstance && inject(piniaSymbol, null)
// 设置为当前活跃的pinia
if (pinia) setActivePinia(pinia)
pinia = activePinia!
// pinia的_s属性为一个map结构,存储键值对:键为storeId,值为store实例
# 从map结构中查询该storeId,如果不存在,则执行创建逻辑
if (!pinia._s.has(id)) {
// creating the store registers it in `pinia._s`
# 新建一个Store实例,并且将它添加到pinia._s属性中
// 根据之前的isSetupStore变量值,划分为两种模式创建逻辑:
if (isSetupStore) {
// setupStore模式
createSetupStore(id, setup, options, pinia)
} else {
// OptionsStore模式
createOptionsStore(id, options as any, pinia)
}
}
# 已存在情况:取出id对应的Store实例
const store: StoreGeneric = pinia._s.get(id)!
# 返回store实例
return store as any
}
useStore
方法一进来调用了getCurrentInstance()
来获取当前的组件实例,也就是说:useStore在哪个组件中调用就会得到它的组件实例,然后在当前组件中注入pinia实例,使用pinia实例,这里拿到pinia实例是为了把即将创建的Store实例注册到Pinia._s
属性中,以及将state
中的数据都挂载到Pinia.state.value[$id]
下面。Pinia._s
属性是一个map结构,在最前面的createPinia
方法有展示过【它的key是storeID,它的值是store实例】。
注意: 这里能够通过
inject(piniaSymbol, null)
获取到pinia,是因为在Install方法里面app.provide()
已经提供了。同时我们应该知道每一个Symbol()
都是唯一的,即Symbol() !== Symbol()
,所以我们可以根据piniaSymbol
可以获取到正确的pinia。
这里根据storeId
来查询Pinia._s
属性值是否存在对应的store实例:
- 存在:则取出并直接返回store实例。
- 不存在:则根据之前
isSetupStore
变量的值来决定创建store实例的逻辑。
综上所述: useStore
方法主要作用就是返回Store实例供我们使用【无则创建,有则直接返回,避免重复创建】。
下面我们开始分析创建store的过程。
有两种定义Store的模式,对应着两种创建Store的方法:
# 1,OptionsStore模式
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
actions: {
increment() {
this.count++
},
},
})
# 2,SetupStore模式
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
})
createOptionsStore
我们首先查看createOptionsStore
源码:
# 创建OptionsStore
function createOptionsStore(id: Id, options, pinia: Pinia, hot?: boolean){
# 从options对象中取出选项参数
const { state, actions, getters } = options
# 获取初始state:根据storeId获取Store中的初始state数据
// pinia.state是一个ref对象数据,这里的value刚开始为一个空对象。所以这里的initialState = undefined
const initialState: StateTree | undefined = pinia.state.value[id]
// 创建store变量
let store: Store<Id, S, G, A>
# 定义setup方法【重要】
function setup() {
// 如果不存在初始化数据,则执行state()
if (!initialState && (!__DEV__ || !hot)) {
if (isVue2) {
// vue2的$set方法,给value对象新增一个storeId属性,值为state数据对象
// 在optionsStore模式下:默认是调用state()返回初始的state对象
set(pinia.state.value, id, state ? state() : {})
} else {
# 给ref数据下新增一个storeId属性,存储初始state数据
// 调用state() 返回值就是初始的state对象
pinia.state.value[id] = state ? state() : {}
}
}
// avoid creating a state in pinia.state.value
# 重点,转换成ref数据
const localState = toRefs(pinia.state.value[id])
// Object.assign() 合并成setupStore原始对象
return assign(
localState,
actions,
Object.keys(getters || {}).reduce((computedGetters, name) => {
// getter
computedGetters[name] = markRaw(
computed(() => {
setActivePinia(pinia)
// it was created just before
const store = pinia._s.get(id)!
// allow cross using stores
/* istanbul ignore next */
if (isVue2 && !store._r) return
// @ts-expect-error
// return getters![name].call(context, context)
// TODO: avoid reading the getter while assigning with a global variable
return getters![name].call(store, store)
})
)
return computedGetters
}, {} as Record<string, ComputedRef>)
)
}
# 调用createSetupStore创建store实例
store = createSetupStore(id, setup, options, pinia, hot, true)
# 返回store实例
return store as any
}
可以看见createOptionsStore
从options选项对象中取出所需要的数据,然后定义了一个setup函数来处理相关的数据【初始化Store的方法】,最后调用了createSetupStore
这个方法来创建了一个Store实例,并且返回这个实例。
综上所述: 当我们使用OptionsStore
模式来定义Store时,createOptionsStore
方法内部会将我们传入数据包装成一个setup函数,最终内部还是调用了createSetupStore
这个方法来创建的Store实例,即将OptionsStore
转换成了SetupStore
类型,所以createSetupStore
函数才是Pinia中创建Store的真正核心,下面我们继续深入createSetupStore
方法源码。
createSetupStore
createSetupStore
方法的源码非常多,有五百多行,这里我们就不展示完整代码了,只贴一些关键的逻辑代码:
# 创建SetupStore
function createSetupStore($id, setup, options, pinia, hot?, isOptionsStore?) {
...
// 获取初始state:根据storeId获取Store中的初始state数据
const initialState = pinia.state.value[$id] | undefined
# 专门处理setupStore模式
if (!isOptionsStore && !initialState && (!__DEV__ || !hot)) {
if (isVue2) {
set(pinia.state.value, $id, {})
} else {
// 初始化为一个空对象
pinia.state.value[$id] = {}
}
}
...
// 定义了一些方法
function $patch() {}
function $reset() {} // 调用$reset()方法可以将 state 重置为初始值
function $dispose() {}
function wrapAction() {}
...
# 定义了一个基础store:并且在它的实例上挂载了一些方法
const partialStore = {
_p: pinia,
// _s: scope,
$id,
$onAction: addSubscription.bind(null, actionSubscriptions),
$patch, // 修改state
$reset, // 重置state
$subscribe() {}, // 监听
$dispose,
}
...
# 使用partialStore为原对象: 创建一个响应式的Store实例
const store = reactive(partialStore)
# 将store添加到pinia._s的map结构中【重点】
pinia._s.set($id, store)
// 调用setup() 生成原始setupStore对象
const setupStore = setup();
# 遍历 setupStore 属性,对其进行处理转换【重点是针对setup模式下的state数据挂载】
for (const key in setupStore) {}
# 将处理后的属性和方法【用户定义的】挂载到:Store实例上
if (isVue2) {
// vue2:通过set方法来新增响应式数据属性
Object.keys(setupStore).forEach((key) => {
set(store, key, setupStore[key])
})
} else {
// vue3:将处理后setupStore中的内容挂载到Store实例上【state数据和actions中的方法】
assign(store, setupStore)
}
# 定义$state访问器属性,可以通过 store.$state 直接修改状态
Object.defineProperty(store, '$state', {
// 访问代理
get: () => pinia.state.value[$id],
// 非直接修改的setter,而是在内部使用了$patch方法
set: (state) => {
$patch(($state) => {
assign($state, state)
})
},
})
# 返回store实例对象
return store
}
createSetupStore
方法里面的内容很多,下面我们贴上一些重点的逻辑逐个解析。
(一)挂载初始state数据对象:
// 获取初始state:根据storeId获取Store中的初始state数据
const initialState = pinia.state.value[$id] | undefined
# 专门处理setupStore模式
if (!isOptionsStore && !initialState && (!__DEV__ || !hot)) {
if (isVue2) {
set(pinia.state.value, $id, {})
} else {
// 初始化为一个空对象
pinia.state.value[$id] = {}
}
}
其实上面这段逻辑在createOptionsStore
方法中也存在,而这里是专门处理setupStore
模式定义store用的,所以这里新增了一个变量isOptionsStore
来做区分。
这段代码主要的作用就是在pinia.state.value
属性下挂载一个空的state数据对象,key为传入的StoreID。
setupStore
模式下:state直接被初始化为一个空对象,因为刚开始它并不知道state会有什么内容,需要经过setup()调用,初始化里面的ref/reactive等响应式数据,处理之后才会挂载到这个空对象里面。
而OptionsStore
模式下:直接调用state函数,即可得到初始的state数据对象。
# 调用state()
pinia.state.value[id] = state ? state() : {}
(二)下面我们再看看$reset方法:
# OptionsStore模式下,重写了$reset方法,
const $reset = isOptionsStore ? function $reset(){} : noop
在OptionsStore
模式下:我们可以通过store.$reset()
重置state数据对象。
而$reset方法的内容其实也很简单:
# $reset方法
function $reset(this: _StoreWithState<Id, S, G, A>) {
// 取出state方法
const { state } = options as DefineStoreOptions<Id, S, G, A>
# 调用state方法:获取初始的state对象
const newState = state ? state() : {}
// 利用$patch方法修改state数据,
this.$patch(($state) => {
// Object.assign:用原始数据覆盖现在的数据
assign($state, newState)
})
}
在setupStore
模式下:**无法通过reset重置∗∗,因为这个模式下state的初始状态是通过调用setup函数得到的。如果我们调用reset重置**,因为这个模式下state的初始状态是通过调用setup函数得到的。如果我们调用reset重置∗∗,因为这个模式下state的初始状态是通过调用setup函数得到的。如果我们调用reset在开发模式会给出以下警告提示,而在生产模式下不会造成任何副作用。
Store "${$id}" is built using the setup syntax and does not implement $reset()
(三)存储已经创建的store实例:
# 将store添加到pinia._s的map结构中【重点】
pinia._s.set($id, store)
在之前的useStore
函数中,我们有通过pinia._s.has(id)
来判断是否已存在目标Store,如果存在则直接返回Store对象。这里用一个map结构来存储已经创建好的Store实例,避免重复新建Store,因为我们可能会在多个组件中引用一个Store对象。
// 多次引用不会重复新建Store
const mainStore = useMainStore();
在Vue3的源码中也存在这样的逻辑处理,使用一个全局的map结构来存储项目中的target与Proxy对象。
(四)下面我们再看看对setupStore
对象的处理:
- 首先是原始store对象的生成:
// setupStore是最原始的store对象
const setupStore = setup();
setupStore
模式下setup函数内容比较简单:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
# 暴露的数据和方法
return { count, increment }
})
调用结果就是return返回的对象。
而OptionsStore
模式下setup内容比较复杂,需要额外的处理:
# OptionsStore的setup初始化过程
function setup() {
# 获取原始state数据
// 调用state() 返回值就是初始的state对象
if (!initialState && (!__DEV__ || !hot)) {
if (isVue2) {
set(pinia.state.value, id, state ? state() : {})
} else {
# 给ref数据下新增一个storeId属性,存储初始state数据
// 调用state() 返回值就是初始的state数据 【普通对象】
pinia.state.value[id] = state ? state() : {}
}
}
// toRefs方法:返回一个普通对象,它的每个属性都是被转换后的ObjectRefImpl数据
// localState对象下的每个属性都是被转换后的ref数据
// { count: ObjectRefImpl{} } 添加这样的代理后,所有针对ObjectRefImpl的修改,实际都是对原对象的修改main: {count:0}
const localState = toRefs(pinia.state.value[id])
# 返回处理后的store数据对象
// 对象合并:将【ref数据/actions下面的方法/getters中的函数】等内容都挂载到结果对象上,并返回
return assign(
localState, // 里面的属性都为ObjectRefImpl数据
actions, // 里面的方法
// 处理getters对象中定义的内容:使用computed(getter)将每个getter函数转换成计算属性
Object.keys(getters || {}).reduce((computedGetters, name) => {})
)
}
OptionsStore
的setup方法内容比较多,主要就是为了处理state数据【将state中的内容全部转换为ref数据】和getters,最后使用对象的Object.assign()
方法将state、actions、getters处理后的内容合并到一个新对象中,并且返回。
所以OptionsStore
的setup方法调用后的返回值:就是我们需要的原始store对象即setupStore。
- 循环处理原始setupStore对象中的数据。
# 循环处理
for (const key in setupStore) {
// 获取val值
const prop = setupStore[key]
// 如果是ref/reactive响应式数据
if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
// 这里并没有处理options模式,因为已经在前面setup()中处理了
# 专门处理setupStore模式
if (!isOptionsStore) {
// 将state中的数据直接挂载到state对象上
if (isVue2) {
set(pinia.state.value[$id], key, prop)
} else {
# 直接将初始的数据挂载到value[$id][key]
// prop为ref/reactive等数据: 直接赋值【相同的引用地址】
pinia.state.value[$id][key] = prop
}
}
# action 方法
// 两个模式公共的逻辑,包装actions的方法
} else if (typeof prop === 'function') {
// prop为函数,使用wrapAction包装处理
const actionValue = wrapAction(key, prop)
# 将包装后的方法重新挂载到setupStore对象
if (isVue2) {
set(setupStore, key, actionValue)
} else {
setupStore[key] = actionValue
}
}
}
- 最后挂载到store实例上:
# 将setupStore中的属性和方法挂载到新建的Store实例上
if (isVue2) {
// vue2:通过set方法来新增响应式数据属性
Object.keys(setupStore).forEach((key) => {
set(store, key, setupStore[key])
})
} else {
// vue3:将处理后setupStore中的内容挂载到Store实例上【state数据和actions中的方法】
assign(store, setupStore)
}
因为这里的store实例是前面新建的响应式数据对象,只存在最基础的属性和方法 【基础版Store】 。
const store = reactive(partialStore)
所以这里需要将我们真正的store中的内容 【即state数据和actions中的方法】 挂载到新建的这个Store实例上,这样这个Store实例才算真正的创建完成,包含了我们所需要的属性和方法。
(五)最后我们再看看 $state属性:
# 定义了一个访问器属性$state
Object.defineProperty(store, '$state', {
// 访问代理
get: () => pinia.state.value[$id],
// 非直接修改的setter,而是在内部使用了$patch方法
set: (state) => {
$patch(($state) => {
# 覆盖key,而非整个替换
assign($state, state)
})
}
})
- getter:可以通过
store.$store
访问state数据,getter内部设置了对应的访问代理。 - setter:这里setter并不是直接去修改store的state,而是内部通过patch来安全的修改:
需要预防出现下面这样的操作:你不能完全替换掉 store 的 state,因为那样会破坏其响应性。
store.$state = { count: 24 }
所以setter内部是通过$patch方法来修改state的,这样即使出现上面这样的代码也不会破坏state的响应性。
3,总结
再总结一下两种模式定义Store的创建过程:
optionsStore模式
- 从
options
对象取出state、getters、actions
。 - 定义
setup
函数。 - 重写
$reset
方法。 - 创建
partialStore
基础store对象,将$patch
、$reset
、$subscribe
等实例方法挂载到该对象上。 - 使用
reactive(partialStore)
方法创建响应式Store对象。 - 将创建好的store对象以键值对方式
($id, store)
注册到Pinia._s
属性中。 - 调用
setup()
函数:设置pinia.state.value[id] = state()
,然后将localState每个属性设置为ObjectRefImpl数据,将getters中的每个getter转换成计算属性,最后将处理完成后的state、getters、actions
合并到一个对象中并返回,生成setupStore
。 - 循环
setupStore
对象,optionsStore
模式在这里仅仅是包装一下actions中的方法。 - 将
setupStore
的内容合并到Store对象。 - 给Store对象定义一个
$state
访问器属性。 - 返回Store对象。
setupStore模式
- 初始化state,设置
pinia.state.value[$id] = {}
空对象。 - 创建
partialStore
基础store对象,将$patch
、$reset
、$subscribe
等实例方法挂载到该对象上。 - 使用
reactive(partialStore)
方法创建响应式Store对象。 - 将创建好的store对象以键值对方式
($id, store)
注册到Pinia._s
属性中。 - 调用
setup()
函数,初始化内部的ref/computed/reactive
响应式数据,获取return返回的setupStore
。 - 循环
setupStore
对象,循环设置pinia.state.value[$id][key] = prop
,同时包装一下actions中的方法。 - 将
setupStore
的内容合并到Store对象。 - 给Store对象定义一个
$state
访问器属性。 - 返回Store对象。
我们再打印查看一下,创建完成的Store实例的内容:
export const useMainStore = defineStore('main', {
state: () => {
return {
count: 0
}
},
actions: {
addCount() {
this.count++
}
}
})
const mainStore = useMainStore()
console.log(mainStore)
查看Store实例的内容:
4,扩展
最后我们再扩展一下,日常我们直接修改state和通过$patch方法修改state的原理区别。
还是按两种模式展示:
// options模式
const mainStore = useMainStore()
// setup模式
const userStore = useUserStore()
直接修改原理
在optionsStore
模式下:实际上是通过ObjectRefImpl
数据内部代理的形式,实现对原对象的修改。
mainStore.count = 1
在前面我们就已经知道了optionsStore
模式下的Store中有一行重要代码:
// pinia.state.value[id] 就是我们在mianStore刚开始的 state对象;{ count: 0 }
const localState = toRefs(pinia.state.value[id])
这里用toRefs
方法:将原始的state对象作为参数,返回一个新对象,其属性都是转换后的ObjectRefImpl
数据,了解toRefs
方法源码的都知道,对ObjectRefImpl
数据的设置,实际上都是会代理到原对象,即state对象。
class ObjectRefImpl<T extends object, K extends keyof T> {
...
set value(newVal) {
// 原理:修改原对象
this._object[this._key] = newVal
}
}
在setupStore
模式下:因为注册时引用的同一个ref数据对象,所以通过userStore.age
的修改,pinia.state.value.user.age
也会变化,因为它们指向了同一个对象。
userStore.age = 1
案例验证:
const userStore = useUserStore()
const p = mainStore._p
// 验证: 指向同一个对象
console.log(p.state.value.user.age === userStore.age) // true
$patch方法修改原理
$patch批量修改方法原理就比较简单了,两种模式都是一样的原理:
# 修改state
// 1,对象参数
store.$patch({
count: store.count + 1,
age: 120,
name: 'DIO',
})
// 2,函数参数
store.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
# patch方法
function $patch( partialStateOrMutator ): void {
// 声明Mutation对象
let subscriptionMutation: SubscriptionCallbackMutation<S>
// 暂停订阅:避免修改state的过程中频繁触发回调
isListening = isSyncListening = false
# 参数为函数的情况:
if (typeof partialStateOrMutator === 'function') {
// 直接调用函数,参数为id对应的state数据
partialStateOrMutator(pinia.state.value[$id])
subscriptionMutation = {
type: MutationType.patchFunction,
storeId: $id,
events: debuggerEvents as DebuggerEvent[],
}
} else {
# 参数为对象的情况:
// 调用mergeReactiveObjects方法,递归合并传入state,覆盖更新value[$id]的key值
mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator)
subscriptionMutation = {
type: MutationType.patchObject,
payload: partialStateOrMutator,
storeId: $id,
events: debuggerEvents as DebuggerEvent[],
}
}
// 下面都是处理监听的内容
const myListenerId = (activeListener = Symbol())
nextTick().then(() => {
if (activeListener === myListenerId) {
isListening = true
}
})
isSyncListening = true
// because we paused the watcher, we need to manually call the subscriptions
// 重新订阅:触发回调
triggerSubscriptions(
subscriptions,
subscriptionMutation,
pinia.state.value[$id] as UnwrapRef<S>
)
}
- 参数为函数的情况下:直接调用函数,参数为目标对象,直接修改属性即可。
- 参数为对象的情况下:递归处理,使用新对象的值,覆盖更新
value[$id][key]
的值。
转载自:https://juejin.cn/post/7212513340508635197