前言
学习pinia源码的笔记算是,只支持setupStore模式。
createPinia
import type { Ref } from 'vue-demi'
import { effectScope, markRaw, ref } from 'vue-demi'
import { type Pinia, piniaSymbol, setActivePinia } from './rootStore'
import type { StateTree } from './types'
export function createPinia() {
// 创建一个effectScope
// 提供管理和隔离响应式数据的能力
const scope = effectScope(true)
// 在创建的 effectScope 中运行一个函数,这个函数返回了一个对象
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({}),
)!
const plugins: Pinia['plugins'] = []
// 标记不能变为响应式数据
const pinia: Pinia = markRaw({
install(app) {
setActivePinia(pinia)
// 关联vue应用实例
pinia.app = app
// 提供一个全局的pinia实例
app.provide(piniaSymbol, pinia)
},
use(plugin) {
// 添加插件
plugins.push(plugin)
},
plugins,
app: null,
scope,
store: new Map<string, any>(),
state,
})
return pinia
}
总结一下就是创建了一个响应式作用域,创建了pinia实例
defineStore
import type { EffectScope } from 'vue-demi'
import { effectScope, hasInjectionContext, inject, reactive } from 'vue-demi'
import { type Pinia, activePinia, piniaSymbol, setActivePinia } from './rootStore'
import type { StateTree, Store, StoreGeneric, _StoreWithState } from './types'
const fallbackRunWithContext = <T>(fn: () => T) => fn()
const { assign } = Object
function createSetupStore<
Id extends string, SS extends Record<any, unknown>,
S extends StateTree,
>(
$id: Id,
setup: () => SS,
pinia: Pinia,
): Store<Id, S> {
// 当前store的scope
let scope!: EffectScope
if (__DEV__ && !pinia.scope.active)
throw new Error('pina destroyed')
// 初始化state
pinia.state.value[$id] = {}
function $reset() {
}
function $dispose() {
}
const partialStore = {
pinia,
$id,
$reset,
$dispose,
} as unknown as _StoreWithState<Id, S>
// 创建一个store
const store: Store<Id, S> = reactive(partialStore) as Store<Id, S>
// 设置到store上
pinia.store.set($id, store)
// https://cn.vuejs.org/api/application.html#app-runwithcontext
const runWithContext
= (pinia.app && pinia.app.runWithContext) || fallbackRunWithContext
// 在当前上下文下运行
// 在pinia总的scope下获取该store的scope
// run steup函数捕获store中所有的响应式数据一起处理
// 拿到的值相当于是store return的值
const setupStore = runWithContext(() =>
pinia.scope.run(() => (scope = effectScope()).run(setup)!),
)!
// 遍历setupStore 设置到store上
for (const key of Object.keys(setupStore)) {
const prop = setupStore[key]
pinia.state.value[$id][key] = prop
// store.$state[key] = prop
}
// 合并store与内置store(提供了一些方法)
assign(store, setupStore)
// 遍历pinia的插件
pinia.plugins.forEach((cb) => {
assign(store, scope.run(() => {
cb({
store,
app: pinia.app,
pinia,
})
}))
})
return store
}
export function defineStore<Id extends string, SS extends Record<any, unknown>>(
id: Id,
// 用于store推演
storeSetup: () => SS,
) {
if (__DEV__ && typeof id !== 'string')
throw new Error('[🍍]: id passed to defineStore must be a string')
function useStore(pinia?: Pinia | null): StoreGeneric {
// 是否有inject的上下文
const hasContext = hasInjectionContext()
// 获取pinia实例
pinia = pinia || (hasContext ? (inject(piniaSymbol, null)) : null)
if (pinia)
setActivePinia(pinia)
if (__DEV__ && !pinia)
throw new Error('[🍍]: pinia not installed. Did you forget to call app.use(pinia)?')
pinia = activePinia!
// 不存在该pinia
if (!pinia.store.has(id)) {
createSetupStore(id, storeSetup, pinia)
if (__DEV__)
// @ts-expect-error: not the right inferred type
useStore._pinia = pinia
}
const store = pinia.store.get(id)!
return store
}
useStore.$id = id
return useStore
}
总结一下就是先从上下文里获取store如果没有再去创建实例,创建时先去设置一些内置的方法与属性,再去通过runWithContext与effectScope获取setupStore中所有的响应式数据并统一管理,此时获取的返回值为setupStore的返回值,将其与内置方法与属性合并存入store集合中
plugin
- pinia实例上的use方法可以注册plugin函数
- 在合并完store后遍历调用plugin函数做处理
类型推导
defineStore函数有两个范型可以获取id与setupStore的类型将类型传入createSetupStore函数中通过类型的处理推导出最终store的类型
测试
pnpm create vite
选择vue3+tspnpm i @baicie/pinia
- 编写两个简单的store
import { createApp } from 'vue'
import './style.css'
import { createPinia } from '@baicie/pinia'
import App from './App.vue'
// 创建pinia实例
const pinia = createPinia()
// 注册plugin
pinia.use(({ store }) => {
console.log('store', store)
})
const app = createApp(App)
app.use(pinia)
app.mount('#app')
import { defineStore } from '@baicie/pinia'
import { reactive } from 'vue'
export const useDemoStore = defineStore('demo', () => {
const state = reactive({
a: 1,
})
const add = () => {
state.a++
}
return {
state,
add,
}
})
export const useDemoStore2 = defineStore('demo2', () => {
const state = reactive({
a: 1,
})
const add = () => {
state.a++
}
return {
state,
add,
}
})
4.使用store
<script setup lang="ts">
import { useDemoStore, useDemoStore2 } from './store'
import HelloWorld from './components/HelloWorld.vue'
const store = useDemoStore()
const store2 = useDemoStore2()
</script>
<template>
<button @click="() => store.add()">
add
</button>
<div>
{{ store.state.a }}
</div>
<HelloWorld msg="msg" />
<button @click="() => store2.add()">
add2
</button>
<div>
{{ store2.state.a }}
</div>
</template>
5.最后效果
最后
代码地址 github.com/baicie/mini…
npm地址 www.npmjs.com/package/@ba…