从createPinia开始
ts
let activePinia: Pinia | undefined
const setActivePinia: _SetActivePinia = (pinia) => (activePinia = pinia)
function createPinia(): Pinia {
const scope = effectScope(true)
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({})
)!
let _p: Pinia['_p'] = []
const pinia: Pinia = markRaw({
install(app: App) {
setActivePinia(pinia)
if (!isVue2) {
pinia._a = app
app.provide(piniaSymbol, pinia)
app.config.globalProperties.$pinia = pinia
}
},
_p, // 记录插件
_a: null, // 连接到当前pinia的App
_e: scope,
_s: new Map<string, StoreGeneric>(), // 记录该pinia注册的各个store
state, // root state
})
return pinia
}
- 截取了部分代码,以上就是pinia实例有哪些属性
有了pinia,可以定义store了
ts
function defineStore(
idOrOptions: any,
setup?: any,
setupOptions?: any
): StoreDefinition {
let id: string
let options:
| DefineStoreOptions<
string,
StateTree,
_GettersTree<StateTree>,
_ActionsTree
>
| DefineSetupStoreOptions<
string,
StateTree,
_GettersTree<StateTree>,
_ActionsTree
>
const isSetupStore = typeof setup === 'function'
if (typeof idOrOptions === 'string') {
id = idOrOptions
options = isSetupStore ? setupOptions : setup
} else {
options = idOrOptions
id = idOrOptions.id
}
function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
pinia = inject(piniaSymbol, null)
if (pinia) setActivePinia(pinia)
pinia = activePinia!
if (!pinia._s.has(id)) { // 判断当前store是否注册在当前pinia中,没有则注册
// 有两种创建方式,一种是setup和不使用setup
if (isSetupStore) {
createSetupStore(id, setup, options, pinia)
} else {
createOptionsStore(id, options as any, pinia)
}
}
const store: StoreGeneric = pinia._s.get(id)! // 注册过就可以拿到实例了
return store as any
}
useStore.$id = id
return useStore
}
- 总结这部分代码,对于
defineStore
只是将当前的pinia获取到后,对于传入的参数进行解析(有两种创建方式,第一种可以使用setup函数,第二种则是对象),判断当前pinia是否注册过当前store,没有则进入注册,后续就可以拿到同一个store实例并返回
创建store挂载到pinia身上
ts
function createSetupStore<
Id extends string,
SS extends Record<any, unknown>,
S extends StateTree,
G extends Record<string, _Method>,
A extends _ActionsTree,
>(
$id: Id,
setup: () => SS,
options:
| DefineSetupStoreOptions<Id, S, G, A>
| DefineStoreOptions<Id, S, G, A> = {},
pinia: Pinia,
hot?: boolean,
isOptionsStore?: boolean
): Store<Id, S, G, A> {
let scope!: EffectScope
let isListening: boolean // set to true at the end
let isSyncListening: boolean // set to true at the end
const initialState = pinia.state.value[$id] as UnwrapRef<S> | undefined // 第一次创建拿不到即为undefined
if (!isOptionsStore && !initialState && (!__DEV__ || !hot)) {
// 第一次拿对于pinia对应的state进行初始化
if (isVue2) {
set(pinia.state.value, $id, {})
} else {
pinia.state.value[$id] = {}
}
}
let activeListener: Symbol | undefined
function $patch(
partialStateOrMutator:
| _DeepPartial<UnwrapRef<S>>
| ((state: UnwrapRef<S>) => void)
): void {
if (typeof partialStateOrMutator === 'function') {
partialStateOrMutator(pinia.state.value[$id] as UnwrapRef<S>) // 传递了函数则执行函数并将对应的state作为参数传入
} else { // 如果是对象形式,则根据参数去state中寻找到对应的值进行更新
mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator)
}
}
// 调用方法,并触发订阅
function wrapAction(name: string, action: _Method) {
return function (this: any) {
setActivePinia(pinia)
const args = Array.from(arguments)
const afterCallbackList: Array<(resolvedReturn: any) => any> = []
const onErrorCallbackList: Array<(error: unknown) => unknown> = []
function after(callback: _ArrayType<typeof afterCallbackList>) {
afterCallbackList.push(callback)
}
function onError(callback: _ArrayType<typeof onErrorCallbackList>) {
onErrorCallbackList.push(callback)
}
// @ts-expect-error
triggerSubscriptions(actionSubscriptions, {
args,
name,
store,
after,
onError,
})
let ret: unknown
try {
ret = action.apply(this && this.$id === $id ? this : store, args)
// handle sync errors
} catch (error) {
triggerSubscriptions(onErrorCallbackList, error)
throw error
}
if (ret instanceof Promise) {
return ret
.then((value) => {
triggerSubscriptions(afterCallbackList, value)
return value
})
.catch((error) => {
triggerSubscriptions(onErrorCallbackList, error)
return Promise.reject(error)
})
}
// trigger after callbacks
triggerSubscriptions(afterCallbackList, ret)
return ret
}
}
const partialStore = {
_p: pinia,
$id,
$onAction: addSubscription.bind(null, actionSubscriptions),
$patch,
} as _StoreWithState<Id, S, G, A>
if (isVue2) {
partialStore._r = false
}
const store: Store<Id, S, G, A> = reactive(partialStore) as unknown as Store<Id, S, G, A> // 包裹成响应式
pinia._s.set($id, store as Store) // 挂载到pinia对应属性中
const runWithContext =
(pinia._a && pinia._a.runWithContext) || fallbackRunWithContext
// 执行setup函数,拿到返回值
const setupStore = runWithContext(() =>
pinia._e.run(() => (scope = effectScope()).run(setup)!)
)!
// overwrite existing actions to support $onAction
for (const key in setupStore) {
const prop = setupStore[key]
if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
if (!isOptionsStore) {
if (initialState && shouldHydrate(prop)) {
if (isRef(prop)) {
prop.value = initialState[key]
} else {
mergeReactiveObjects(prop, initialState[key])
}
}
// transfer the ref to the pinia state to keep everything in sync
/* istanbul ignore if */
if (isVue2) {
set(pinia.state.value[$id], key, prop)
} else {
pinia.state.value[$id][key] = prop
}
}
} else if (typeof prop === 'function') {
const actionValue = __DEV__ && hot ? prop : wrapAction(key, prop)
if (isVue2) {
set(setupStore, key, actionValue)
} else {
setupStore[key] = actionValue
}
}
}
// 根据传入参数更新store
if (isVue2) {
Object.keys(setupStore).forEach((key) => {
set(store, key, setupStore[key])
})
} else {
assign(store, setupStore)
// allows retrieving reactive objects with `storeToRefs()`. Must be called after assigning to the reactive object.
// Make `storeToRefs()` work with `reactive()` #799
assign(toRaw(store), setupStore)
}
// use this instead of a computed with setter to be able to create it anywhere
// without linking the computed lifespan to wherever the store is first
// created.
Object.defineProperty(store, '$state', {
get: () => pinia.state.value[$id],
set: (state) => {
$patch(($state) => {
assign($state, state)
})
},
})
return store
}
- 对于
createOptionsStore
方法则是写一个setup方法对options进行处理,然后调用createSetupStore
方法 - 总结这部分代码,就是对于传入的参数state和action分别进行处理,然后挂载到当前活跃的pinia实例属性上,针对id作为唯一键名区别不同的store,同时也避免多次创建(单例模式)
和vuex对比
-vuex源码,这篇文章有写vuex的大致实现
- pinia提供了composition-api的写法
- 没有modules嵌套结构和命名空间,取而代之的是多个store实例
- 不需要动态添加store,对于pinia直接使用useXxxStore,第一次使用的时候会挂载到当前
createPinia
产生的实例属性上 - 没有冗余的mutations