Object.prototype.toString.call(o) === '[object Object]' &&
typeof o.toJSON !== 'function'
export function addSubscription(
subscriptions.push(callback)
const removeSubscription = () => {
const idx = subscriptions.indexOf(callback)
subscriptions.splice(idx, 1)
if (!detached && getCurrentScope()) {
onScopeDispose(removeSubscription)
return removeSubscription
export function triggerSubscriptions(
// 拷贝一份 subscriptions, 防止在执行回调时 subscriptions 发生变化
subscriptions.slice().forEach((callback) => {
function mergeReactiveObjects(target, patchToApply) {
if (target instanceof Map && patchToApply instanceof Map) {
patchToApply.forEach((value, key) => target.set(key, value))
if (target instanceof Set && patchToApply instanceof Set) {
patchToApply.forEach(target.add, target)
// 无需遍历 Symbol,因为它们无论如何都无法序列化
for (const key in patchToApply) {
if (!patchToApply.hasOwnProperty(key)) continue
const subPatch = patchToApply[key]
const targetValue = target[key]
// 如果是对象类型的值且它本身不是响应式对象, 则递归调用 mergeReactiveObjects
isPlainObject(targetValue) &&
isPlainObject(subPatch) &&
target.hasOwnProperty(key) &&
// isRef/isReactive 是 vue 内部用于判断是否是响应式对象的方法
// NOTE: 在这里,我想警告不一致的类型,但这是不可能的,因为在设置存储中,人们可能会将属性的值启动为某种类型,例如 一个 Map,然后出于某种原因,在 SSR 期间,将其更改为 "undefined"。 当尝试水合时,我们想用 "undefined" 覆盖 Map。
target[key] = mergeReactiveObjects(targetValue, subPatch)
function createSetupStore(
const optionsForPlugin = assign(
// 对象型的整个 store,或者是函数型 store 的第三个参数
// 开发环境如果 effectScope 的 active 为 false 则表示 pinia 被提前销毁了
if (__DEV__ && !pinia._e.active) {
throw new Error('Pinia destroyed')
// watcher options for $subscribe
// $subscribe 功能的实现其实就是添加了一个 watch 函数来监听变量的变化, 这个对象就是 watch 的第三个参数
const $subscribeOptions = {
if (__DEV__ && !isVue2) {
// vue3 watch 支持 onTrigger 参数, 这个参数可以让底层框架做一些其他的事, 而不是将用户传入的参数包装一层再调用
$subscribeOptions.onTrigger = (event) => {
// 当 store 正在创建中并且 pinia 正在更新中则不触发这个事件
} else if (isListening == false && !store._hotUpdating) {
if (Array.isArray(debuggerEvents)) {
debuggerEvents.push(event)
'🍍 debuggerEvents should be an array. This is most likely an internal Pinia bug.'
let actionSubscriptions = []
const initialState = pinia.state.value[$id]
// 如果是 setup store 则不需要初始化 state
if (!isOptionsStore && !initialState && (!__DEV__ || !hot)) {
set(pinia.state.value, $id, {})
pinia.state.value[$id] = {}
// https://github.com/vuejs/pinia/issues/1129
// $patch 函数的主要是用于覆盖现有store的状态, 在需要批量更新store的时候有奇效
// 用来覆盖 store 数据的状态或者是一个函数
isListening = isSyncListening = false
// 由于 $patch 是同步的,所以每次触发的时候重置 debuggerEvents
/* istanbul ignore else */
if (typeof partialStateOrMutator === 'function') {
// 如果是函数则执行函数, 并把 store 作为参数传入
partialStateOrMutator(pinia.state.value[$id])
// 设置更新值得 mutation 参数, 用于告诉通知 subscriptions 时的变更类型
type: MutationType.patchFunction,
mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator)
type: MutationType.patchObject,
payload: partialStateOrMutator,
const myListenerId = (activeListener = Symbol())
if (activeListener === myListenerId) {
// 因为我们暂停了观察者,所以我们需要手动调用订阅
// 如果是 options store 则可以调用 $reset 方法还原成初始值
const $reset = isOptionsStore
const { state } = options
const newState = state ? state() : {}
// 因为这个 $reset 最终会被放到一个单独的对象上,所以 this.$patch 其实就是上面的 $patch 函数,只是内部覆盖值的操作被简化成了直接覆盖整个对象
this.$patch(($state) => {
: /* istanbul ignore next */
`🍍: Store "${$id}" is built using the setup syntax and does not implement $reset().`
* 包装 action 以处理订阅, 在值发生变化以后触发订阅
* @param name - actions 里的 key
* @param action - actions 里的 value
* @returns 返回一个包装了触发订阅参数的 action
function wrapAction(name, action) {
// 在每次操作前更新一次 pinia,确保实例的正确
const args = Array.from(arguments)
const afterCallbackList = []
const onErrorCallbackList = []
function after(callback) {
afterCallbackList.push(callback)
function onError(callback) {
onErrorCallbackList.push(callback)
triggerSubscriptions(actionSubscriptions, {
// 用户获取判断 action 是否是异步的(返回一个 promise)
ret = action.apply(this && this.$id === $id ? this : store, args)
// 如果 action 执行出错, 触发 onError 订阅函数
triggerSubscriptions(onErrorCallbackList, error)
// 如果 action 是异步的, 触发 after 订阅函数
if (ret instanceof Promise) {
triggerSubscriptions(afterCallbackList, value)
triggerSubscriptions(onErrorCallbackList, error)
return Promise.reject(error)
// 如果是同步的函数则在执行完成后触发 after 订阅函数
triggerSubscriptions(afterCallbackList, ret)
const _hmrPayload = markRaw({
$onAction: addSubscription.bind(null, actionSubscriptions),
$subscribe(callback, options = {}) {
const removeSubscription = addSubscription(
// 清理函数, 用于取消 scopeEffect 收集的副作用
const stopWatcher = scope.run(() =>
() => pinia.state.value[$id],
if (options.flush === 'sync' ? isSyncListening : isListening) {
type: MutationType.direct,
events: debuggerEvents as DebuggerEvent,
assign({}, $subscribeOptions, options)
// 返回一个清理函数, 常用的 API 设计风格了
return removeSubscription
__DEV__ || (__USE_DEVTOOLS__ && IS_CLIENT)
// 如果是开发环境且开启了 devtools, 则添加 hmr 对象同时将 store 作为响应式对象
_customProperties: markRaw(new Set()), // devtools custom properties
// 现在存储部分存储,以便存储的设置可以在完成之前相互实例化,而不会创建无限循环。
(pinia._a && pinia._a.runWithContext) || fallbackRunWithContext
// TODO: idea 创建 skipSerialize 将属性标记为不可序列化并跳过它们
const setupStore = runWithContext(() =>
pinia._e.run(() => (scope = effectScope()).run(setup)!)
for (const key in setupStore) {
const prop = setupStore[key]
// 必须得是 ref/reactive 同时不能是 computed
if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
set(hotState.value, key, toRef(setupStore as any, key))
} else if (!isOptionsStore) { // createOptionStore 直接在 pinia.state.value 中设置状态,因此可以跳过, 只需要处理普通的对象 store
// 在设置存储中,我们必须对状态进行水合,并将pinia状态树与用户刚刚创建的 refs 同步
if (initialState && shouldHydrate(prop)) {
prop.value = initialState[key]
// @ts-expect-error: prop is unknown
mergeReactiveObjects(prop, initialState[key])
// 将 ref 转移到 pinia 内部状态以保持一切同步处理
set(pinia.state.value[$id], key, prop)
pinia.state.value[$id][key] = prop
/* istanbul ignore else */
_hmrPayload.state.push(key)
} else if (typeof prop === 'function') {
// 这是一个热模块替换 store,因为 hotUpdate 方法需要在正确的上下文中执行此操作
// 所以如果是开发环境并且启用了热更新则不要包装它
const actionValue = __DEV__ && hot ? prop : wrapAction(key, prop)
set(setupStore, key, actionValue)
setupStore[key] = actionValue
_hmrPayload.actions[key] = prop
// 列出 actions,以便可以在插件中使用它们
optionsForPlugin.actions[key] = prop
_hmrPayload.getters[key] = isOptionsStore
const getters = (setupStore._getters) || ((setupStore._getters = markRaw([])))
// 添加 state、getters 和 actions 属性
Object.keys(setupStore).forEach((key) => {
set(store, key, setupStore[key])
assign(store, setupStore)
// 允许使用“storeToRefs()”检索反应对象。 必须在分配给反应对象后调用。 让“storeToRefs()”与“reactive()”一起使用 #799
assign(toRaw(store), setupStore)
// 使用它而不是使用 setter 计算,以便能够在任何地方创建它,而无需将计算的生命周期链接到首次创建存储的位置。
Object.defineProperty(store, '$state', {
get: () => (__DEV__ && hot ? hotState.value : pinia.state.value[$id]),
throw new Error('cannot set hotState')
// 在插件之前添加 hotUpdate 以允许它们覆盖它
store._hotUpdate = markRaw((newStore) => {
store._hotUpdating = true
newStore._hmrPayload.state.forEach((stateKey) => {
if (stateKey in store.$state) {
const newStateTarget = newStore.$state[stateKey]
const oldStateSource = store.$state[stateKey]
typeof newStateTarget === 'object' &&
isPlainObject(newStateTarget) &&
isPlainObject(oldStateSource)
patchObject(newStateTarget, oldStateSource)
newStore.$state[stateKey] = oldStateSource
// 修补直接访问属性以允许 store.stateProperty 用作 store.$state.stateProperty
set(store, stateKey, toRef(newStore.$state, stateKey))
Object.keys(store.$state).forEach((stateKey) => {
if (!(stateKey in newStore.$state)) {
pinia.state.value[$id] = toRef(newStore._hmrPayload, 'hotState')
for (const actionName in newStore._hmrPayload.actions) {
const action = newStore[actionName]
set(store, actionName, wrapAction(actionName, action))
// TODO: 不确定这在 setup store 和 option store 中是否都有效
for (const getterName in newStore._hmrPayload.getters) {
const getter = newStore._hmrPayload.getters[getterName]
const getterValue = isOptionsStore
? // option store 中的 getters 的特殊处理
return getter.call(store, store)
set(store, getterName, getterValue)
Object.keys(store._hmrPayload.getters).forEach((key) => {
if (!(key in newStore._hmrPayload.getters)) {
Object.keys(store._hmrPayload.actions).forEach((key) => {
if (!(key in newStore._hmrPayload.actions)) {
// 更新 devtools 中使用的值并允许稍后删除新属性
store._hmrPayload = newStore._hmrPayload
store._getters = newStore._getters
store._hotUpdating = false
if (__USE_DEVTOOLS__ && IS_CLIENT) {
;(['_p', '_hmrPayload', '_getters', '_customProperties'] as const).forEach(
assign({ value: store[p] }, nonEnumerable)
pinia._p.forEach((extender) => {
if (__USE_DEVTOOLS__ && IS_CLIENT) {
// 插件内部可能会为 store 添加新的属性, 这些属性可能是响应式的, 所以要将他们收集起来
const extensions = scope.run(() =>
options: optionsForPlugin,
Object.keys(extensions || {}).forEach((key) =>
// 把新增的属性添加到 store._customProperties 中, 以便在 devtools 中显示
store._customProperties.add(key)
assign(store, extensions)
// 执行同样的操作, 将插件返回的值和 store 合并
options: optionsForPlugin,
// 对传入的 state 进行格式判断, 不允许是一个自定义类或内置类, 只能是传统对象, 同样只在开发器间做判断
typeof store.$state === 'object' &&
typeof store.$state.constructor === 'function' &&
!store.$state.constructor.toString().includes('[native code]')
`[🍍]: The "state" must be a plain object. It cannot be\n` +
`\tstate: () => new MyClass()\n` +
`Found in store "${store.$id}".`
// 只把初始状态下的 pinia store 用作 SSR 时的初始状态
// 可以自定义 hydrate 方法, 用于在 SSR 时, 将初始状态同步到 store 中
// 表示 store 已经被创建, 内部状态正在监听