从源码视角来看Pinia!

一、Pinia 概览

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

Pinia 核心代码集中在:

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

二、createPinia 全局容器

2.1 createPinia 核心

TypeScript 复制代码
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
TypeScript 复制代码
const scope = effectScope(true)

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

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

② 全局 state 容器
TypeScript 复制代码
const state = ref({})

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

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

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

三、defineStore 定义 store

TypeScript 复制代码
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 核心流程

TypeScript 复制代码
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
TypeScript 复制代码
const scope = effectScope()

层级关系:

Plain 复制代码
Pinia Root Scope
 └── Store Scope
      ├── computed
      ├── watch
      └── effect

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

② store 是 reactive 包裹的对象
TypeScript 复制代码
const store = reactive({})

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

所以:

TypeScript 复制代码
store.count
store.double
store.increment

全部是同一个 reactive proxy。

如果通过 Setup Store 创建:

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

如果是 options store :

TypeScript 复制代码
state: () => ({ count: 0 })
TypeScript 复制代码
pinia.state.value[id] = reactive(state())

然后:

TypeScript 复制代码
store.count -> toRef(pinia.state.value[id], 'count')

所以:

TypeScript 复制代码
store.count++ // 实际修改的是 pinia.state

四、getters 的底层原理(computed)

Options Store 的 getter:

TypeScript 复制代码
getters: {
  double: (state) => state.count * 2
}

源码实现本质:

TypeScript 复制代码
computed(() => {
  setActivePinia(pinia)
  return getter.call(store, store)
})

本质结论:

Pinia getter == Vue computed

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

五、actions

TypeScript 复制代码
actions: {
  increment() {
    this.count++
  }
}

源码:

TypeScript 复制代码
function wrapAction(name, action) {
  return function () {
    return action.apply(store, arguments)
  }
}

六、其他 API

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

TypeScript 复制代码
store.$patch({
  count: store.count + 1
})

源码:

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

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

TypeScript 复制代码
store.$subscribe((mutation, state) => {})

源码:

TypeScript 复制代码
watch(
  () => pinia.state.value[id],
  (state) => callback(),
  { deep: true }
)

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

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

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

TypeScript 复制代码
const { count } = storeToRefs(store)

源码:

TypeScript 复制代码
if (isRef(value) || isReactive(value)) {
  refs[key] = toRef(store, key)
}

storeToRefs = 批量 toRef

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

相关推荐
梦6502 小时前
JavaScript 循环
开发语言·javascript·ecmascript
运筹vivo@2 小时前
攻防世界: mfw
前端·web安全·php
沛沛老爹3 小时前
从Web到AI:行业专属Agent Skills生态系统技术演进实战
java·开发语言·前端·vue.js·人工智能·rag·企业转型
GGGG寄了3 小时前
HTML——列表标签
前端·html5
HWL56793 小时前
显示器缩放和更改分辨率的区别
前端·css·vue.js·计算机外设·html5
jzshmyt3 小时前
曼德勃罗集web可视化应用
前端
GGGG寄了4 小时前
HTML——表格的基本用法
前端·html
yanyu-yaya4 小时前
速学兼复习之vue3章节3
前端·javascript·vue.js·学习·前端框架