从源码视角来看Pinia!

一、Pinia 概览

Pinia 本质是:「基于 Vue3 响应式系统 + effectScope 的“全局可控副作用容器”」

Pinia 核心代码集中在:

packages/pinia/src/
├── createPinia.ts
├── rootStore.ts
├── store.ts
├── subscriptions.ts
├── types.ts
┌─────────────────────┐
│   用户 API 层        │  defineStore / storeToRefs
├─────────────────────┤
│   Store 实现层       │  setupStore / optionsStore
├─────────────────────┤
│   响应式 & 调度层     │  reactive / effectScope / watch
└─────────────────────┘

二、createPinia 全局容器

2.1 createPinia 核心

export function createPinia() {
  const scope = effectScope(true)
  const state = scope.run(() => ref({}))!

  const pinia = markRaw({
    _e: scope,
    _s: new Map(), // store 注册
    state,
    install(app) {
      setActivePinia(pinia)
      app.provide(piniaSymbol, pinia)
    }
  })

  return pinia
}

① 全局 effectScope

const scope = effectScope(true)

所有 store 的 effect / computed / watch,全部挂在这个全局 scope(作用域) 下

这意味着 pinia._e.stop() 就能一次性销毁所有 store 副作用

② 全局 state 容器

const state = ref({})

Pinia 并不是每个 store 自己维护 root state。

而是 pinia 统一管理,然后由 storeId 区分 state 属于哪个 store:

pinia.state.value[storeId] = storeState // storeId 就是我们通过 defineStore 创建 store 的第一个参数

三、defineStore 定义 store

export function defineStore(id, setupOrOptions) {
  return function useStore() {
    const pinia = getActivePinia()
    if (!pinia._s.has(id)) {
      createStore(id, setupOrOptions, pinia)
    }
    return pinia._s.get(id)
  }
}

Pinia 内部其实有 ​两种 store​:

3.1 setupStore 核心流程

function setupStore(id, setup, pinia) {
  const scope = effectScope()
  const store = reactive({})

  const setupResult = scope.run(() =>
    setup({ action })
  )

  for (const key in setupResult) {
    const prop = setupResult[key]
    store[key] = prop
  }

  pinia._s.set(id, store)
}

① 每个 store 自己也有一个 effectScope

const scope = effectScope()

层级关系:

Pinia Root Scope
 └── Store Scope
      ├── computed
      ├── watch
      └── effect

所以,store.$dispose() ⇒ stop 当前 store 的所有副作用,不影响其他 store

② store 是 reactive 包裹的对象

const store = reactive({})

​注意:​Pinia ​不包装 state,​Pinia 包装的是整个 store

所以:

store.count
store.double
store.increment

全部是同一个 reactive proxy。

如果通过 Setup Store 创建:

const count = ref(0)
return { count }
store.count === count // true // 本质上 Pinia 直接复用 Vue 原生响应式对象

如果是 options store :

state: () => ({ count: 0 })
pinia.state.value[id] = reactive(state())

然后:

store.count -> toRef(pinia.state.value[id], 'count')

所以:

store.count++ // 实际修改的是 pinia.state

四、getters 的底层原理(computed)

Options Store 的 getter:

getters: {
  double: (state) => state.count * 2
}

源码实现本质:

computed(() => {
  setActivePinia(pinia)
  return getter.call(store, store)
})

本质结论:

Pinia getter == Vue computed

  • 依赖收集由 Vue 完成
  • 脏检查、缓存完全交给 computed

五、actions

actions: {
  increment() {
    this.count++
  }
}

源码:

function wrapAction(name, action) {
  return function () {
    return action.apply(store, arguments)
  }
}

六、其他 API

6.1 $patch:为什么比直接改 state 更好?

store.$patch({
  count: store.count + 1
})

源码:

pauseTracking()
applyPatch() // 批量更新,暂停追踪依赖
resumeTracking()
triggerSubscriptions() // 统一触发

6.2 $subscribe / watch 的关系(副作用系统)

store.$subscribe((mutation, state) => {})

源码:

watch(
  () => pinia.state.value[id],
  (state) => callback(),
  { deep: true }
)

Pinia 的 subscribe 就是一个封装过的 watch

但多做了:mutation 类型、时间戳、devtools hook

6.3 storeToRefs:为什么不会丢响应式?

const { count } = storeToRefs(store)

源码:

if (isRef(value) || isReactive(value)) {
  refs[key] = toRef(store, key)
}

storeToRefs = 批量 toRef

七、Pinia vs Vuex(源码级简单对比)

posted @ 2026-01-22 23:49  秀秀不只会前端  阅读(0)  评论(0)    收藏  举报