从源码视角来看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

浙公网安备 33010602011771号