从源码视角来看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(源码级简单对比)

相关推荐
HelloReader10 分钟前
Tauri 架构从“WebView + Rust”到完整工具链与生态
前端
UIUV21 分钟前
node:child_process spawn 模块学习笔记
javascript·后端·node.js
Bigger23 分钟前
告别版本焦虑:如何为 Hugo 项目定制专属构建环境
前端·架构·go
烛阴1 小时前
Three.js 零基础入门:手把手打造交互式 3D 几何体展示系统
javascript·webgl·three.js
颜酱2 小时前
单调栈:从模板到实战
javascript·后端·算法
代码匠心2 小时前
AI 自动编程:一句话设计高颜值博客
前端·ai·ai编程·claude
_AaronWong3 小时前
Electron 实现仿豆包划词取词功能:从 AI 生成到落地踩坑记
前端·javascript·vue.js
cxxcode3 小时前
I/O 多路复用:从浏览器到 Linux 内核
前端
用户5433081441944 小时前
AI 时代,前端逆向的门槛已经低到离谱 — 以 Upwork 为例
前端
JarvanMo4 小时前
Flutter 版本的 material_ui 已经上架 pub.dev 啦!快来抢先体验吧。
前端