💚 Pinia 状态管理设计

Vue 3 官方状态管理库源码深度解析

基于 vuejs/pinia v2 源码
2026-03-16 | 技术深度解读

📑 目录

第一部分:基础概念

  • Pinia 简介
  • 核心架构
  • 架构演进

第二部分:核心实现

  • createPinia 源码
  • defineStore 实现
  • Store 创建流程

第三部分:核心功能

  • $patch 状态更新
  • $subscribe 订阅
  • $onAction 监听

第四部分:高级特性

  • 插件系统
  • DevTools 集成
  • HMR 热更新

💚 Pinia 简介

Pinia 是 Vue 3 的官方状态管理库,名字来源于西班牙语的"菠萝"

核心特点

  • ✅ 完整的 TypeScript 支持
  • ✅ 极简 API 设计
  • ✅ 支持 Options 和 Setup 两种写法
  • ✅ 模块化设计,无需嵌套
  • ✅ 轻量级(~1KB gzipped)

与 Vuex 对比

  • ❌ 无 mutations
  • ❌ 无模块嵌套
  • ✅ 更好的 TypeScript 支持
  • ✅ 更少的样板代码
  • ✅ 更直观的 API

🏗️ 核心架构

┌─────────────────────────────────────────┐
│              Vue Application            │
│  ┌─────────────────────────────────┐   │
│  │         app.use(pinia)          │   │
│  └─────────────────────────────────┘   │
└─────────────────────────────────────────┘
                    │
                    ▼
┌─────────────────────────────────────────┐
│           Pinia Instance                │
│  ┌───────────┐  ┌──────────────────┐   │
│  │   state   │  │   _s (stores)    │   │
│  │  (全局状态) │  │   Map<id, store>│   │
│  └───────────┘  └──────────────────┘   │
│  ┌───────────────────────────────────┐  │
│  │      _p (plugins)                 │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
                    │
        ┌───────────┼───────────┐
        ▼           ▼           ▼
   ┌─────────┐ ┌─────────┐ ┌─────────┐
   │ Store A │ │ Store B │ │ Store C │
   └─────────┘ └─────────┘ └─────────┘

📈 架构演进

版本 时间 主要变化
Vuex 3.x 2017 Vue 2 官方状态管理,mutations + actions
Vuex 4.x 2020 Vue 3 兼容版本
Pinia 0.x 2019 实验性项目,探索新 API
Pinia 1.x 2021 稳定版本,Vue 3 默认推荐
Pinia 2.x 2022 完整 Vue 2/3 支持,Setup Store

💡 设计理念:Keep it Simple, Stupid (KISS)

🔧 createPinia 源码

export function createPinia(): Pinia {
  const scope = effectScope(true)
  
  // 创建全局状态(响应式)
  const state = scope.run<Ref<Record<string, StateTree>>>(() =>
    ref<Record<string, StateTree>>({})
  )!

  let _p: Pinia['_p'] = []  // 插件列表
  let toBeInstalled: PiniaPlugin[] = []

  const pinia: Pinia = markRaw({
    install(app: App) {
      setActivePinia(pinia)
      pinia._a = app
      app.provide(piniaSymbol, pinia)
      app.config.globalProperties.$pinia = pinia
      
      // 安装待安装的插件
      toBeInstalled.forEach((plugin) => _p.push(plugin))
      toBeInstalled = []
    },
    use(plugin) { ... },
    _p,        // 插件列表
    _a: null,  // Vue app 实例
    _e: scope, // effectScope
    _s: new Map<string, StoreGeneric>(), // store 缓存
    state,     // 全局状态
  })

  return pinia
}

📦 Pinia 实例结构

interface Pinia {
  // 插件列表
  _p: PiniaPlugin[]
  
  // Vue app 实例
  _a: App | null
  
  // effectScope 用于管理副作用作用域
  _e: EffectScope
  
  // Store 缓存 Map
  _s: Map<string, StoreGeneric>
  
  // 全局状态(响应式 ref)
  state: Ref<Record<string, StateTree>>
  
  // 安装方法
  install(app: App): void
  
  // 使用插件
  use(plugin: PiniaPlugin): Pinia
}

💡 关键点:所有 Store 共享同一个 state 对象,便于 SSR 序列化

📝 defineStore 概述

三种调用方式:

// 1. Options Store(传统写法)
export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    double: (state) => state.count * 2,
  },
  actions: {
    increment() { this.count++ },
  },
})

// 2. Setup Store(推荐)
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const double = computed(() => count.value * 2)
  function increment() { count.value++ }
  return { count, double, increment }
})

// 3. 仅 ID + Options
export const useCounterStore = defineStore({
  id: 'counter',
  state: () => ({ count: 0 }),
})

📋 Options Store

function createOptionsStore<Id, S, G, A>(
  id: Id,
  options: DefineStoreOptions<Id, S, G, A>,
  pinia: Pinia,
  hot?: boolean
): Store<Id, S, G, A> {
  const { state, actions, getters } = options

  function setup() {
    // 初始化状态
    if (!initialState) {
      pinia.state.value[id] = state ? state() : {}
    }
    
    const localState = toRefs(pinia.state.value[id])
    
    // 合并 state、actions、getters
    return assign(
      localState,
      actions,
      Object.keys(getters || {}).reduce((computedGetters, name) => {
        computedGetters[name] = markRaw(
          computed(() => {
            setActivePinia(pinia)
            const store = pinia._s.get(id)!
            return getters![name].call(store, store)
          })
        )
        return computedGetters
      }, {})
    )
  }

  return createSetupStore(id, setup, options, pinia, hot, true)
}

⚡ Setup Store

优势

  • ✅ 更灵活的组织方式
  • ✅ 可以使用任何组合式 API
  • ✅ 更好的 TypeScript 推断
  • ✅ 自由引用其他 store

类型推断

// 自动推断类型
const useStore = defineStore(
  'main',
  () => {
    const count = ref(0)
    // count: Ref<number>
    
    const double = computed(
      () => count.value * 2
    )
    // double: ComputedRef<number>
    
    return { count, double }
  }
)

🔧 createSetupStore 源码(核心)

function createSetupStore($id, setup, options, pinia, hot, isOptionsStore) {
  let scope: EffectScope
  
  // 订阅回调列表
  let subscriptions: SubscriptionCallback<S>[] = []
  let actionSubscriptions: StoreOnActionListener[] = []
  
  // 部分 Store 对象(核心方法)
  const partialStore = {
    _p: pinia,
    $id,
    $onAction: addSubscription.bind(null, actionSubscriptions),
    $patch,
    $reset,
    $subscribe(callback, options = {}) {
      const removeSubscription = addSubscription(
        subscriptions, callback, options.detached, 
        () => stopWatcher()
      )
      const stopWatcher = scope.run(() =>
        watch(() => pinia.state.value[$id], callback, options)
      )
      return removeSubscription
    },
    $dispose,
  }

  // 创建响应式 store
  const store = reactive(partialStore)
  
  // 执行 setup 函数
  const setupStore = pinia._e.run(() => 
    (scope = effectScope()).run(() => setup({ action }))
  )!
  
  // 合并 setupStore 到 store
  assign(store, setupStore)
  
  return store
}

🔄 Store 初始化流程

sequenceDiagram
    participant U as User Code
    participant D as defineStore
    participant C as createSetupStore
    participant P as Pinia
    participant S as Store

    U->>D: defineStore('id', setup)
    D->>D: 返回 useStore 函数
    U->>D: useStore()
    D->>P: 检查 Store 缓存 _s
    alt Store 不存在
        D->>C: createSetupStore()
        C->>C: 创建 effectScope
        C->>C: 执行 setup()
        C->>C: 遍历 setup 返回值
        loop 每个属性
            alt 是 ref/reactive
                C->>P: 添加到 pinia.state
            else 是 function
                C->>C: 包装为 action
            else 是 computed
                C->>C: 标记为 getter
            end
        end
        C->>P: 缓存到 _s
    end
    D->>S: 返回 Store 实例

🎯 $patch 方法

两种调用方式:

// 1. 对象式 patch
store.$patch({
  count: store.count + 1,
  name: 'New Name'
})

// 2. 函数式 patch(推荐用于复杂更新)
store.$patch((state) => {
  state.items.push('new item')
  state.count++
  state.changedAt = Date.now()
})

💡 性能优化:多次修改会合并为一次更新,减少 watcher 触发

🔗 mergeReactiveObjects

function mergeReactiveObjects<T>(
  target: T, 
  patchToApply: _DeepPartial<T>
): T {
  // 处理 Map 类型
  if (target instanceof Map && patchToApply instanceof Map) {
    patchToApply.forEach((value, key) => target.set(key, value))
  }
  
  // 处理 Set 类型
  if (target instanceof Set && patchToApply instanceof Set) {
    patchToApply.forEach(target.add, target)
  }

  // 递归合并普通对象
  for (const key in patchToApply) {
    const subPatch = patchToApply[key]
    const targetValue = target[key]
    
    if (
      isPlainObject(targetValue) &&
      isPlainObject(subPatch) &&
      !isRef(subPatch) &&
      !isReactive(subPatch)
    ) {
      target[key] = mergeReactiveObjects(targetValue, subPatch)
    } else {
      target[key] = subPatch
    }
  }

  return target
}

👂 $subscribe 订阅

// 订阅状态变化
const unsubscribe = store.$subscribe(
  (mutation, state) => {
    console.log('Type:', mutation.type)
    console.log('Store ID:', mutation.storeId)
    console.log('New State:', state)
    
    // mutation.type 可能是:
    // - 'direct': 直接修改 store.count = 1
    // - 'patch object': store.$patch({ count: 1 })
    // - 'patch function': store.$patch(state => state.count++)
  },
  { detached: true, deep: true, flush: 'sync' }
)

// 取消订阅
unsubscribe()

💡 detached: 即使组件卸载,订阅仍然有效

🎬 $onAction 监听

// 监听 action 调用
const unsubscribe = store.$onAction(
  ({ name, args, after, onError, store }) => {
    console.log(`Action ${name} called with:`, args)
    
    // action 成功完成后执行
    after((result) => {
      console.log(`Action ${name} finished:`, result)
    })
    
    // action 失败时执行
    onError((error) => {
      console.error(`Action ${name} failed:`, error)
    })
  }
)

// 异步 action 支持
await store.fetchData() // after 回调会等待 Promise resolve

🎁 Action 包装器

const action = <Fn extends _Method>(fn: Fn, name: string): Fn => {
  const wrappedAction = function (this: any) {
    setActivePinia(pinia)
    const args = Array.from(arguments)

    const afterCallbackList: Array<(resolvedReturn: any) => any> = []
    const onErrorCallbackList: Array<(error: unknown) => unknown> = []

    // 触发 $onAction 订阅
    triggerSubscriptions(actionSubscriptions, {
      args,
      name: wrappedAction[ACTION_NAME],
      store,
      after: (cb) => afterCallbackList.push(cb),
      onError: (cb) => onErrorCallbackList.push(cb),
    })

    try {
      let ret = fn.apply(store, args)
      
      // 处理异步 action
      if (ret instanceof Promise) {
        return ret
          .then((value) => {
            triggerSubscriptions(afterCallbackList, value)
            return value
          })
          .catch((error) => {
            triggerSubscriptions(onErrorCallbackList, error)
            return Promise.reject(error)
          })
      }
      
      triggerSubscriptions(afterCallbackList, ret)
      return ret
    } catch (error) {
      triggerSubscriptions(onErrorCallbackList, error)
      throw error
    }
  } as MarkedAction<Fn>

  return wrappedAction
}

🔄 $reset 重置

// Options Store 才支持 $reset
const $reset = isOptionsStore
  ? function $reset() {
      const { state } = options
      const newState = state ? state() : {}
      
      // 使用 $patch 统一更新,触发订阅
      this.$patch(($state) => {
        assign($state, newState)
      })
    }
  : __DEV__
    ? () => {
        throw new Error(
          `🍍: Store "${$id}" is built using the setup syntax ` +
          `and does not implement $reset().`
        )
      }
    : noop

// 使用示例
store.$reset() // 重置到初始状态

💡 Setup Store 不支持 $reset:因为无法确定初始状态

🗑️ $dispose 销毁

function $dispose() {
  // 停止 effectScope,清理所有副作用
  scope.stop()
  
  // 清空订阅
  subscriptions = []
  actionSubscriptions = []
  
  // 从 Pinia 缓存中移除
  pinia._s.delete($id)
}

// 使用场景
// 1. 测试中清理
afterEach(() => {
  store.$dispose()
})

// 2. 动态创建的 store
const dynamicStore = createDynamicStore()
dynamicStore.$dispose()

⚠️ 注意:$dispose 不会删除 pinia.state.value 中的状态

⚡ 响应式系统

// Store 使用 reactive 包装
const store = reactive({
  $id,
  $patch,
  $subscribe,
  // ... 其他属性
})

// 状态使用 ref
const state = ref<Record<string, StateTree>>({})

// Setup Store 的 ref 会被收集到 pinia.state
if (isRef(prop) && !isComputed(prop)) {
  pinia.state.value[$id][key] = prop
}

// Computed 用于 getters
const double = computed(() => {
  setActivePinia(pinia)
  return getters![name].call(store, store)
})

🎯 effectScope 管理

Pinia 级别

const scope = effectScope(true)
const state = scope.run(() =>
  ref<Record<string, StateTree>>({})
)
pinia._e = scope

所有 Store 共享同一个 effectScope

Store 级别

let scope: EffectScope
const setupStore = pinia._e.run(() =>
  (scope = effectScope())
    .run(() => setup({ action }))
)

// $dispose 时停止
function $dispose() {
  scope.stop()
}

每个 Store 有独立的 effectScope

📊 状态存储结构

// 全局状态结构
pinia.state.value = {
  // Store 1 的状态
  'user': {
    name: 'Chuck',
    email: '[email protected]',
    roles: ['admin', 'user']
  },
  
  // Store 2 的状态
  'cart': {
    items: [
      { id: 1, name: 'Product A', qty: 2 },
      { id: 2, name: 'Product B', qty: 1 }
    ],
    total: 199.99
  },
  
  // Store 3 的状态
  'settings': {
    theme: 'dark',
    language: 'zh-CN'
  }
}

// SSR 序列化
const state = JSON.stringify(pinia.state.value)

🔌 插件系统

// 插件定义
interface PiniaPlugin {
  (context: {
    pinia: Pinia
    app: App
    store: Store
    options: DefineStoreOptionsInPlugin
  }): object | void
}

// 使用插件
const pinia = createPinia()
pinia.use(({ store }) => {
  // 添加全局属性
  store.$router = router
  
  // 添加全局方法
  store.$log = (msg) => console.log(`[${store.$id}]`, msg)
  
  return { $router, $log }
})

// 所有 store 都会获得这些属性
const store = useUserStore()
store.$log('Hello') // [user] Hello

🔔 订阅机制

// 三种订阅方式
store.$subscribe(callback)      // 状态变化订阅
store.$onAction(callback)       // action 调用订阅
pinia.use(plugin)               // 插件订阅(全局)

// 内部实现
let subscriptions: SubscriptionCallback[] = []
let actionSubscriptions: StoreOnActionListener[] = []

// 添加订阅
function addSubscription(list, callback, detached, onCleanup) {
  list.push(callback)
  return () => {
    const idx = list.indexOf(callback)
    if (idx > -1) {
      list.splice(idx, 1)
      onCleanup?.()
    }
  }
}

// 触发订阅
function triggerSubscriptions(list, ...args) {
  list.slice().forEach(callback => callback(...args))
}

➕ addSubscription 源码

export function addSubscription<T extends _Method>(
  subscriptions: T[],
  callback: T,
  detached?: boolean,
  onCleanup: () => void = noop
) {
  subscriptions.push(callback)

  const removeSubscription = () => {
    const idx = subscriptions.indexOf(callback)
    if (idx > -1) {
      subscriptions.splice(idx, 1)
      onCleanup()
    }
  }

  // 如果不是 detached 且在 effectScope 中
  // 自动在 scope dispose 时清理
  if (!detached && getCurrentScope()) {
    onScopeDispose(removeSubscription)
  }

  return removeSubscription
}

💡 自动清理:非 detached 订阅会在组件卸载时自动清理

🚀 triggerSubscriptions 源码

export function triggerSubscriptions<T extends _Method>(
  subscriptions: T[],
  ...args: Parameters<T>
) {
  // 使用 slice() 防止迭代时修改列表
  subscriptions.slice().forEach((callback) => {
    callback(...args)
  })
}

// 使用示例
// 触发 $subscribe 回调
triggerSubscriptions(
  subscriptions,
  mutation,
  pinia.state.value[$id]
)

// 触发 $onAction 回调
triggerSubscriptions(
  actionSubscriptions,
  { args, name, store, after, onError }
)

💡 slice() 保护:防止回调中修改订阅列表导致迭代异常

🔷 类型系统

// Store 类型定义
export type Store<
  Id extends string = string,
  S extends StateTree = {},
  G = {},
  A = {},
> = _StoreWithState<Id, S, G, A> &
  UnwrapRef<S> &
  _StoreWithGetters<G> &
  A &
  PiniaCustomProperties<Id, S, G, A>

// 类型推断
export type StoreState<SS> = SS extends Store<string, infer S, any, any>
  ? UnwrapRef<S>
  : never

export type StoreGetters<SS> = SS extends Store<string, any, infer G, any>
  ? _StoreWithGetters<G>
  : never

export type StoreActions<SS> = SS extends Store<string, any, any, infer A>
  ? A
  : never

🔍 Store 类型推导

// Setup Store 类型推断
export type _ExtractStateFromSetupStore<SS> = 
  Pick<SS, _ExtractStateFromSetupStore_Keys<SS>>

export type _ExtractActionsFromSetupStore<SS> = 
  Pick<SS, _ExtractActionsFromSetupStore_Keys<SS>>

export type _ExtractGettersFromSetupStore<SS> = 
  Pick<SS, _ExtractGettersFromSetupStore_Keys<SS>>

// Keys 提取
type _ExtractStateFromSetupStore_Keys<SS> = keyof {
  [K in keyof SS as SS[K] extends _Method | ComputedRef ? never : K]: any
}

type _ExtractActionsFromSetupStore_Keys<SS> = keyof {
  [K in keyof SS as SS[K] extends _Method ? K : never]: any
}

type _ExtractGettersFromSetupStore_Keys<SS> = keyof {
  [K in keyof SS as SS[K] extends ComputedRef ? K : never]: any
}

📝 MutationType 枚举

export enum MutationType {
  /**
   * 直接修改状态
   * store.name = 'new name'
   * store.$state.name = 'new name'
   * store.list.push('new item')
   */
  direct = 'direct',

  /**
   * 使用对象式 $patch
   * store.$patch({ name: 'newName' })
   */
  patchObject = 'patch object',

  /**
   * 使用函数式 $patch
   * store.$patch(state => state.name = 'newName')
   */
  patchFunction = 'patch function',
}

// 在 subscribe 回调中使用
store.$subscribe((mutation, state) => {
  switch (mutation.type) {
    case MutationType.direct:
      console.log('Direct mutation')
      break
    case MutationType.patchObject:
      console.log('Patch object:', mutation.payload)
      break
    case MutationType.patchFunction:
      console.log('Patch function')
      break
  }
})

🔧 DevTools 集成

// createPinia 中自动注册
if (__USE_DEVTOOLS__ && IS_CLIENT && typeof Proxy !== 'undefined') {
  pinia.use(devtoolsPlugin)
}

// DevTools 插件功能
const devtoolsPlugin = ({ store, pinia }) => {
  // 1. 注册 store 到 devtools
  registerPiniaDevtools(app, pinia)
  
  // 2. 提供 HMR 支持
  if (__DEV__) {
    store._hotUpdate = (newStore) => {
      // 热更新逻辑
    }
  }
  
  // 3. 自定义属性标记
  store._customProperties = new Set()
  store._hmrPayload = {
    state: [],
    actions: {},
    getters: {},
  }
}

🔥 HMR 热更新

// vite.config.js
export default defineConfig({
  plugins: [vue()],
})

// store 中使用
if (import.meta.hot) {
  import.meta.hot.accept(
    acceptHMRUpdate(useCounterStore, import.meta.hot)
  )
}

// acceptHMRUpdate 实现
export function acceptHMRUpdate(
  initialUseStore: StoreDefinition,
  hot: any
) {
  // 返回一个接受新 store 的函数
  return (newModule) => {
    const newUseStore = newModule[Object.keys(newModule)[0]]
    
    if (newUseStore.$id !== initialUseStore.$id) {
      console.warn('Store ID changed, skipping HMR')
      return
    }
    
    // 获取现有 store 并热更新
    const existingStore = pinia._s.get(newUseStore.$id)
    if (existingStore) {
      existingStore._hotUpdate(newUseStore())
    }
  }
}

🌐 SSR 支持

// 服务器端
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)

// 渲染前获取数据
await useUserStore().fetchUser()

// 序列化状态
const state = JSON.stringify(pinia.state.value)

// 客户端
const pinia = createPinia()
// 注水(恢复状态)
pinia.state.value = JSON.parse(window.__PINIA_STATE__)

const app = createApp(App)
app.use(pinia)

// hydrate 选项(自定义注水)
export const useStore = defineStore('main', {
  state: () => ({
    date: useLocalStorage('date', new Date())
  }),
  hydrate(storeState, initialState) {
    storeState.date = useLocalStorage('date', new Date())
  }
})

🎨 设计模式

使用的模式

  • ✅ 单例模式
  • ✅ 工厂模式
  • ✅ 观察者模式
  • ✅ 代理模式
  • ✅ 依赖注入

架构特点

  • 📝 函数式 API 设计
  • 📦 响应式数据绑定
  • 🔌 插件化架构
  • 🎯 类型安全

🔒 单例模式

// Pinia 单例
let activePinia: Pinia | undefined

export function setActivePinia(pinia: Pinia) {
  activePinia = pinia
}

export function getActivePinia() {
  return activePinia
}

// Store 单例缓存
function useStore(pinia?: Pinia | null): StoreGeneric {
  pinia = pinia || getActivePinia()
  
  // 检查缓存
  if (!pinia._s.has(id)) {
    // 创建新 store
    if (isSetupStore) {
      createSetupStore(id, setup, options, pinia)
    } else {
      createOptionsStore(id, options, pinia)
    }
  }
  
  // 返回缓存的 store
  return pinia._s.get(id)!
}

👁️ 观察者模式

// 订阅者列表
let subscriptions: SubscriptionCallback[] = []
let actionSubscriptions: StoreOnActionListener[] = []

// 注册观察者
store.$subscribe((mutation, state) => {
  console.log('State changed:', mutation)
})

store.$onAction(({ name, args }) => {
  console.log('Action called:', name)
})

// 通知观察者
function $patch(partialStateOrMutator) {
  // ... 修改状态
  
  triggerSubscriptions(
    subscriptions,
    { type: 'patch object', storeId: $id },
    pinia.state.value[$id]
  )
}

// 在 action 中通知
triggerSubscriptions(actionSubscriptions, {
  args, name, store, after, onError
})

🏭 工厂模式

// defineStore 是工厂函数的工厂
export function defineStore(id, setup, options) {
  // 返回工厂函数
  function useStore(pinia?: Pinia | null): StoreGeneric {
    pinia = pinia || getActivePinia()
    
    if (!pinia._s.has(id)) {
      // 根据类型创建不同的 store
      if (isSetupStore) {
        createSetupStore(id, setup, options, pinia)
      } else {
        createOptionsStore(id, options, pinia)
      }
    }
    
    return pinia._s.get(id)!
  }
  
  useStore.$id = id
  return useStore
}

// 使用
const useCounterStore = defineStore('counter', () => { ... })
const store = useCounterStore() // 工厂创建实例

🎭 代理模式

// $state 代理
Object.defineProperty(store, '$state', {
  get: () => pinia.state.value[$id],
  set: (state) => {
    $patch(($state) => {
      assign($state, state)
    })
  }
})

// action 代理
const wrappedAction = function () {
  setActivePinia(pinia)
  // 前置处理
  
  try {
    return fn.apply(store, args)
    // 后置处理
  } catch (error) {
    // 错误处理
  }
}

// 响应式代理
const store = reactive(partialStore)

📊 数据流图

graph TD
    A[Component] -->|useStore| B[Store]
    B -->|read| C[State]
    B -->|call| D[Actions]
    B -->|computed| E[Getters]
    
    D -->|$patch| C
    C -->|reactive| F[Vue Reactivity]
    F -->|trigger| G[Watchers]
    G -->|update| A
    
    D -->|$onAction| H[Action Subscribers]
    C -->|$subscribe| I[State Subscribers]
    
    J[Plugins] -->|extend| B
    K[DevTools] -->|inspect| B
    
    style C fill:#42b883
    style D fill:#ff6b35
    style E fill:#61dafb

🔄 状态更新流程

sequenceDiagram
    participant C as Component
    participant S as Store
    participant P as $patch
    participant R as Reactivity
    participant W as Watchers

    C->>S: store.count = 1
    S->>R: 触发 reactive set
    R->>W: 通知 watchers
    W->>C: 重新渲染

    C->>S: store.$patch({ count: 2 })
    S->>P: 调用 $patch
    P->>P: 暂停监听 isListening = false
    P->>P: mergeReactiveObjects
    P->>P: 恢复监听 isListening = true
    P->>W: triggerSubscriptions
    W->>C: 重新渲染

🎬 Action 调用流程

sequenceDiagram
    participant C as Component
    participant W as WrappedAction
    participant S as Subscribers
    participant A as OriginalAction
    participant R as Result

    C->>W: store.increment()
    W->>W: setActivePinia(pinia)
    W->>S: triggerSubscriptions (before)
    S->>S: 注册 after/onError 回调
    W->>A: fn.apply(store, args)
    
    alt Success
        A->>R: return result
        alt Is Promise
            R->>R: .then(value)
            R->>S: trigger after callbacks
        else Sync
            W->>S: trigger after callbacks
        end
        W->>C: return result
    else Error
        A-->>W: throw error
        W->>S: trigger onError callbacks
        W-->>C: throw error
    end

⚡ 性能优化

优化策略

  • ✅ 批量更新($patch)
  • ✅ 延迟初始化
  • ✅ effectScope 管理
  • ✅ markRaw 避免代理
  • ✅ 订阅自动清理

性能数据

指标 数值
包体积 ~1KB gzipped
创建 Store <1ms
$patch 更新 <0.1ms

🔄 批量更新优化

function $patch(partialStateOrMutator) {
  let subscriptionMutation: SubscriptionCallbackMutation
  
  // 暂停 watcher,避免多次触发
  isListening = isSyncListening = false
  debuggerEvents = []
  
  if (typeof partialStateOrMutator === 'function') {
    // 函数式:用户自己修改
    partialStateOrMutator(pinia.state.value[$id])
  } else {
    // 对象式:合并对象
    mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator)
  }
  
  // 恢复 watcher
  const myListenerId = (activeListener = Symbol())
  nextTick().then(() => {
    if (activeListener === myListenerId) {
      isListening = true
    }
  })
  isSyncListening = true
  
  // 统一触发订阅(只触发一次)
  triggerSubscriptions(subscriptions, subscriptionMutation, state)
}

🌳 Tree Shaking

// 未使用的 store 会被 tree-shake
export const useUserStore = defineStore('user', () => { ... })
export const useCartStore = defineStore('cart', () => { ... })
export const useSettingsStore = defineStore('settings', () => { ... })

// 只使用 user store
import { useUserStore } from './stores'
const user = useUserStore()
// cart 和 settings 的代码不会打包

// defineStore 标记为无副作用
/*! #__NO_SIDE_EFFECTS__ */
export function defineStore(...): StoreDefinition {
  // ...
}

// mapHelpers 使用动态导入
export const mapStores = __DEV__ 
  ? createMapHelper()
  : noop

📊 与 Vuex 对比

特性 Vuex Pinia
Mutations ✅ 必须 ❌ 移除
模块嵌套 ✅ 需要 ❌ 扁平化
TypeScript ⚠️ 需要额外配置 ✅ 开箱即用
DevTools ✅ 支持 ✅ 支持
SSR ✅ 支持 ✅ 支持
包体积 ~3KB ~1KB
Setup 语法 ❌ 不支持 ✅ 支持

✅ 最佳实践

推荐做法

  • ✅ 使用 Setup Store
  • ✅ Store 按功能拆分
  • ✅ 使用组合式函数复用逻辑
  • ✅ 合理使用 $patch 批量更新
  • ✅ 异步操作放在 actions 中

代码组织

// stores/
// ├── user.ts
// ├── cart.ts
// └── settings.ts

// user.ts
export const useUserStore = defineStore(
  'user',
  () => {
    const user = ref(null)
    const isLoggedIn = computed(() => !!user.value)
    
    async function login(credentials) {
      user.value = await api.login(credentials)
    }
    
    return { user, isLoggedIn, login }
  }
)

❌ 反模式

// ❌ 直接解构(丢失响应性)
const { count, double } = useCounterStore()
// count 和 double 不是响应式的!

// ✅ 使用 storeToRefs
import { storeToRefs } from 'pinia'
const { count, double } = storeToRefs(useCounterStore())

// ❌ 在 getter 中修改状态
getters: {
  doubleCount(state) {
    state.count++ // 不要这样做!
    return state.count * 2
  }
}

// ❌ 嵌套 store
const useParentStore = defineStore('parent', () => {
  const childStore = useChildStore() // 可能导致循环依赖
})

// ✅ 在需要时获取 store
const useParentStore = defineStore('parent', () => {
  const getChildStore = () => useChildStore()
})

❓ 常见问题

Q1: 如何在组件外使用 Store?

// ❌ 错误
const store = useUserStore()

// ✅ 正确
import { getActivePinia } from 'pinia'
const store = useUserStore(
  getActivePinia()
)

Q2: 如何重置 Store?

// Options Store
store.$reset()

// Setup Store
const initial = { count: 0 }
const useStore = defineStore('id', () => {
  const count = ref(initial.count)
  const $reset = () => {
    count.value = initial.count
  }
  return { count, $reset }
})

📚 扩展阅读

官方资源

相关技术

  • 💚 Vue 3 Composition API
  • 🔷 TypeScript 类型体操
  • ⚛️ Vue Reactivity System
  • 🔥 Vite HMR API

🎯 总结

核心要点

  • ✅ 极简 API,无 mutations
  • ✅ 完整 TypeScript 支持
  • ✅ Options/Setup 两种写法
  • ✅ 插件化架构
  • ✅ 优秀的 DevTools 支持

源码亮点

  • 📝 effectScope 管理副作用
  • 🔗 reactive 创建响应式 Store
  • 🎯 订阅模式实现观察者
  • ⚡ $patch 批量更新优化
  • 🔷 完善的类型推导

💡 Pinia 证明了简单的设计往往是最强大的

📖 参考资料

资源 链接
Pinia 官方文档 pinia.vuejs.org
GitHub 仓库 github.com/vuejs/pinia
Vue 3 文档 vuejs.org
Vue Reactivity Reactivity in Depth
TypeScript Handbook typescriptlang.org

感谢阅读!💚

访问链接:atcfu.com/ai-articles/pinia-state-management/