如下解析基于 2.0.28 版本
基本使用
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
//vue2 使用
import { createPinia, PiniaVuePlugin } from 'pinia'
Vue.use(PiniaVuePlugin)
const pinia = createPinia()
new Vue({
el: '#app',
pinia,
})
//ceatePinia.ts
export function createPinia(): Pinia {
// 创建个scope effct来单独管理state
const scope = effectScope(true)
// 通过pinia.state.value[storeId]会保存之后创建的store的state
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({})
)!
// 插件相关
let _p: Pinia['_p'] = []
// plugins added before calling app.use(pinia)
let toBeInstalled: PiniaPlugin[] = []
// markRaw标记pinia不可被代理,避免存在用户将其响应式化影响性能的情况
const pinia: Pinia = markRaw({
install(app: App) {
// 设置当前活跃的pinia,方便其他地方获取
setActivePinia(pinia)
if (!isVue2) {
pinia._a = app
// 内部通过inject获取pinia,因为piniaSymbol没有导出,所以开发者无法通过inject获取pinia
app.provide(piniaSymbol, pinia)
// 通过app.config.globalProperties将pinia挂载到组件实例上
app.config.globalProperties.$pinia = pinia
if (USE_DEVTOOLS) {
registerPiniaDevtools(app, pinia)
}
toBeInstalled.forEach((plugin) => _p.push(plugin))
toBeInstalled = []
}
},
// pinia插件相关,暂不分析
use(plugin) {
if (!this._a && !isVue2) {
toBeInstalled.push(plugin)
} else {
_p.push(plugin)
}
return this
},
_p,
// it's actually undefined here
// @ts-expect-error
_a: null,
_e: scope,
// 每个创建的store都会放在这个Map里
_s: new Map<string, StoreGeneric>(),
state,
})
//...
return pinia
}
//rootStore.ts
export let activePinia: Pinia | undefined
export const setActivePinia = (pinia: Pinia | undefined) =>
(activePinia = pinia)
// 如果在vue组件中,通过inject获取(在createPinia中导出)、否则直接取全局变量
export const getActivePinia = () =>
(getCurrentInstance() && inject(piniaSymbol)) || activePinia
PiniaVuePlugin
//vue2-plugin.ts
export const PiniaVuePlugin: Plugin = function (_Vue) {
_Vue.mixin({
beforeCreate() {
const options = this.$options
if (options.pinia) {
// 获取注册到根组件上的pinia
const pinia = options.pinia as Pinia
// 通过Object.defineProperty实现的hack版provid、inject...
if (!(this as any)._provided) {
const provideCache = {}
Object.defineProperty(this, '_provided', {
get: () => provideCache,
set: (v) => Object.assign(provideCache, v),
})
}
;(this as any)._provided[piniaSymbol as any] = pinia
if (!this.$pinia) {
this.$pinia = pinia
}
pinia._a = this as any
if (IS_CLIENT) {
// this allows calling useStore() outside of a component setup after
// installing pinia's plugin
setActivePinia(pinia)
}
if (USE_DEVTOOLS) {
registerPiniaDevtools(pinia._a, pinia)
}
} else if (!this.$pinia && options.parent && options.parent.$pinia) {
this.$pinia = options.parent.$pinia
}
},
destroyed() {
delete this._pStores
},
})
}
创建store的三种方式
const useUserStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
}
}
})
// or
const useUserStore = defineStore({
id: 'counter',
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
}
}
})
// or
const useUserStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
})
export function defineStore(
// TODO: add proper types from above
idOrOptions: any,
setup?: any,
setupOptions?: any
): StoreDefinition {
let id: string
let options
const isSetupStore = typeof setup === 'function'
// 抹平不同格式参数
if (typeof idOrOptions === 'string') {
id = idOrOptions
options = isSetupStore ? setupOptions : setup
} else {
options = idOrOptions
id = idOrOptions.id
}
// 最终交给开发者获取store的方法
function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
const currentInstance = getCurrentInstance()
// 如果存在currentInstance说明在vue组件中,通过inject获取到pinia
pinia =
(__TEST__ && activePinia && activePinia._testing ? null : pinia) ||
(currentInstance && inject(piniaSymbol, null))
//设置当前活跃的pinia对象,如果存在多个pinia对象,方便快速获取当前pinia对象
if (pinia) setActivePinia(pinia)
if (__DEV__ && !activePinia) {
throw new Error(
`[]: getActivePinia was called with no active Pinia. Did you forget to install pinia?\n` +
`\tconst pinia = createPinia()\n` +
`\tapp.use(pinia)\n` +
`This will fail in production.`
)
}
// 从rootStore文件中取的全局变量
pinia = activePinia!
// 如果没有创建过id对应的store,则会调用createSetupStore或createOptionsStore进行创建
if (!pinia._s.has(id)) {
// 区分第二个参数是函数还是options对象
if (isSetupStore) {
createSetupStore(id, setup, options, pinia)
} else {
createOptionsStore(id, options as any, pinia)
}
/* istanbul ignore else */
if (__DEV__) {
// @ts-expect-error: not the right inferred type
useStore._pinia = pinia
}
}
// 获取上面创建好的store,pinia._s是一个Map结构
const store: StoreGeneric = pinia._s.get(id)!
// 热更新相关,重新创建更新store
if (__DEV__ && hot) {
const hotId = '__hot:' + id
const newStore = isSetupStore
? createSetupStore(hotId, setup, options, pinia, true)
: createOptionsStore(hotId, assign({}, options) as any, pinia, true)
hot._hotUpdate(newStore)
// cleanup the state properties and the store from the cache
delete pinia.state.value[hotId]
pinia._s.delete(hotId)
}
// 往当前组件实例上缓存store,主要是给devtools使用
if (
__DEV__ &&
IS_CLIENT &&
currentInstance &&
currentInstance.proxy &&
// avoid adding stores that are just built for hot module replacement
!hot
) {
const vm = currentInstance.proxy
const cache = '_pStores' in vm ? vm._pStores! : (vm._pStores = {})
cache[id] = store
}
// StoreGeneric cannot be casted towards Store
return store as any
}
useStore.$id = id
return useStore
}
如果defineStore传入的是一个setup函数,则会调用此方法创建store
function createSetupStore(
$id: Id,
setup: () => SS,
options:
| DefineSetupStoreOptions<Id, S, G, A>
| DefineStoreOptions<Id, S, G, A> = {},
pinia: Pinia,
hot?: boolean,
isOptionsStore?: boolean
){
let scope!: EffectScope //为setup函数返回的内容单独建立一个scope
if (__DEV__ && !pinia._e.active) { //pinia._e为创建pinia时建立的scope
throw new Error('Pinia destroyed')
}
const initialState = pinia.state.value[$id] as UnwrapRef<S> | undefined
// 如果pinia.state.value[storeId]未初始化,进行初始化
if (!isOptionsStore && !initialState && (!__DEV__ || !hot)) {
if (isVue2) {
set(pinia.state.value, $id, {})
} else {
pinia.state.value[$id] = {}
}
}
//...
// 对外暴露的store内容
const partialStore = {
_p: pinia,
// _s: scope,
$id,
// 调用$onAction会将回调加入到actionSubscriptions中,wrapAction内会触发回调
$onAction: addSubscription.bind(null, actionSubscriptions),
$patch,
$reset,
$subscribe(callback, options = {}) {
const removeSubscription = addSubscription(
subscriptions,
callback,
options.detached,
() => stopWatcher()
)
// 监听state直接修改
const stopWatcher = scope.run(() =>
watch(
() => pinia.state.value[$id] as UnwrapRef<S>,
(state) => {
// flush默认为'pre',而在调用$patch时isListening会被设置为false,所以不会触发$patch修改state的监听回调
if (options.flush === 'sync' ? isSyncListening : isListening) {
callback(
{
storeId: $id,
type: MutationType.direct,
events: debuggerEvents as DebuggerEvent,
},
state
)
}
},
assign({}, $subscribeOptions, options)
)
)!
return removeSubscription
},
$dispose,
} as _StoreWithState<Id, S, G, A>
// 返回的store是个reactive对象
const store: Store<Id, S, G, A> = reactive(
__DEV__ || USE_DEVTOOLS
? assign(
{
_hmrPayload,
_customProperties: markRaw(new Set<string>()), // devtools custom properties
},
partialStore
// must be added later
// setupStore
)
: partialStore
) as unknown as Store<Id, S, G, A>
// 将store注册到pinia上
pinia._s.set($id, store)
}
function createSetupStore(
$id: Id,
setup: () => SS,
options:
| DefineSetupStoreOptions<Id, S, G, A>
| DefineStoreOptions<Id, S, G, A> = {},
pinia: Pinia,
hot?: boolean,
isOptionsStore?: boolean
){
// ...
// 将defineStore使用者定义的返回内容放进scope中,在组件卸载时回收effect
const setupStore = pinia._e.run(() => {
scope = effectScope()
return scope.run(() => setup())
})!
// ...
}
function createSetupStore(
$id: Id,
setup: () => SS,
options:
| DefineSetupStoreOptions<Id, S, G, A>
| DefineStoreOptions<Id, S, G, A> = {},
pinia: Pinia,
hot?: boolean,
isOptionsStore?: boolean
){
// ...
// 将defineStore使用者定义的返回内容放进scope中,在组件卸载时回收effect
const setupStore = pinia._e.run(() => {
scope = effectScope()
return scope.run(() => setup())
})!
for (const key in setupStore) {
const prop = setupStore[key]
// 只处理ref、reactive对象,computed等不处理
if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
// mark it as a piece of state to be serialized
if (__DEV__ && hot) {
// 热更新相关,忽略
set(hotState.value, key, toRef(setupStore as any, key))
// option结构已经在createOptionsStore将其加入pinia
} else if (!isOptionsStore) {// 同步pinia.state -> store
// 将用户可能直接调用pinia.state.value[$id]设置的ref、reactive对象设置到setup返回的结果上,让二者的响应式都代理一个对象
// 使得store、pinia能同步更改
if (initialState && shouldHydrate(prop)) {
if (isRef(prop)) {
prop.value = initialState[key]
} else {
// probably a reactive object, lets recursively assign
// 同步其他类型
mergeReactiveObjects(prop, initialState[key])
}
}
// transfer the ref to the pinia state to keep everything in sync
// 将setup返回的ref、reactive对象同步到pinia.state上,使得store、pinia能同步更改
if (isVue2) { //同步 store -> pinia.state
set(pinia.state.value[$id], key, prop)
} else {
pinia.state.value[$id][key] = prop
}
}
}
}
// 合并方法
function mergeReactiveObjects<
T extends Record<any, unknown> | Map<unknown, unknown> | Set<unknown>
>(target: T, patchToApply: _DeepPartial<T>): T {
// 合并Map类型
if (target instanceof Map && patchToApply instanceof Map) {
patchToApply.forEach((value, key) => target.set(key, value))
}
// 合并Set类型
if (target instanceof Set && patchToApply instanceof Set) {
patchToApply.forEach(target.add, target)
}
for (const key in patchToApply) {
if (!patchToApply.hasOwnProperty(key)) continue
const subPatch = patchToApply[key]
const targetValue = target[key]
// 只有普通对象才会进入递归
if (
isPlainObject(targetValue) &&
isPlainObject(subPatch) &&
target.hasOwnProperty(key) &&
!isRef(subPatch) &&
!isReactive(subPatch)
) {
target[key] = mergeReactiveObjects(targetValue, subPatch)
} else {
target[key] = subPatch
}
}
return target
}
function createSetupStore(
$id: Id,
setup: () => SS,
options:
| DefineSetupStoreOptions<Id, S, G, A>
| DefineStoreOptions<Id, S, G, A> = {},
pinia: Pinia,
hot?: boolean,
isOptionsStore?: boolean
){
// ...
// 将defineStore使用者定义的返回内容放进scope中,在组件卸载时回收effect
const setupStore = pinia._e.run(() => {
scope = effectScope()
return scope.run(() => setup())
})!
for (const key in setupStore) {
const prop = setupStore[key]
// 只处理ref、reactive对象,computed等不处理
if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
// ...
} else if (typeof prop === 'function') {
// 将setup中的方法替换成wrapAction包装的方法,wrapAction在订阅发布章节解析
const actionValue = __DEV__ && hot ? prop : wrapAction(key, prop)
if (isVue2) {
set(setupStore, key, actionValue)
} else {
// @ts-expect-error
setupStore[key] = actionValue
}
/* istanbul ignore else */
if (__DEV__) {
_hmrPayload.actions[key] = prop
}
// list actions so they can be used in plugins
// @ts-expect-error
optionsForPlugin.actions[key] = prop
}
}
}
function createSetupStore(
$id: Id,
setup: () => SS,
options:
| DefineSetupStoreOptions<Id, S, G, A>
| DefineStoreOptions<Id, S, G, A> = {},
pinia: Pinia,
hot?: boolean,
isOptionsStore?: boolean
){
// ...
// 对外暴露的store API
const partialStore = {
//...
}
const store: Store<Id, S, G, A> = reactive(
//...
partialStore
)
// 将defineStore使用者定义的返回内容放进scope中,在组件卸载时回收effect
const setupStore = pinia._e.run(() => {
scope = effectScope()
return scope.run(() => setup())
})!
for (const key in setupStore) {
// ...
}
// 将最同步后的结果,合并进store上
if (isVue2) {
Object.keys(setupStore).forEach((key) => {
set(store, key, setupStore[key])
})
} else {
// 将store的reactive对象、原始对象都进行合并
assign(store, setupStore)
assign(toRaw(store), setupStore)
}
}
当传入的是option配置时,则会调用此方法创建store
function createOptionsStore<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
A extends _ActionsTree
>(
id: Id,
options: DefineStoreOptions<Id, S, G, A>,
pinia: Pinia,
hot?: boolean
): Store<Id, S, G, A> {
const { state, actions, getters } = options
const initialState: StateTree | undefined = pinia.state.value[id]
let store: Store<Id, S, G, A>
// 将options转化成setup函数
function setup() {
// 初始化pinia.state
if (!initialState && (!__DEV__ || !hot)) {
if (isVue2) {
set(pinia.state.value, id, state ? state() : {})
} else {
pinia.state.value[id] = state ? state() : {}
}
}
// 将pinia.state全部转换成ref,和setup中返回ref效果一致
const localState =
__DEV__ && hot
? // use ref() to unwrap refs inside state TODO: check if this is still necessary
toRefs(ref(state ? state() : {}).value)
: toRefs(pinia.state.value[id])
// 最终options store返回的内容格式和setup store返回的内容格式一致
return assign(
localState,
actions,
// 将 getter 转换成 computed
Object.keys(getters || {}).reduce((computedGetters, name) => {
if (__DEV__ && name in localState) {
console.warn(
`[]: A getter cannot have the same name as another state property. Rename one of them. Found with "${name}" in store "${id}".`
)
}
// getter函数不可代理,及不对computed做额外处理,和setup中一致
computedGetters[name] = markRaw(
computed(() => {
setActivePinia(pinia)
// it was created just before
const store = pinia._s.get(id)!
// allow cross using stores
if (isVue2 && !store._r) return
// @ts-expect-error
// return getters![name].call(context, context)
return getters![name].call(store, store)
})
)
return computedGetters
}, {} as Record<string, ComputedRef>)
)
}
// option选项注册的pinia会被转换成setup函数形式
store = createSetupStore(id, setup, options, pinia, hot, true)
store.$reset = function $reset() {
const newState = state ? state() : {}
// we use a patch to group all changes into one single subscription
this.$patch(($state) => {
assign($state, newState)
})
}
return store as any
}
pinia能够对修改state、action进行监听,内部通过发布订阅模式、watch API实现
//subscriptions.ts
export function addSubscription<T extends _Method>(
subscriptions: T[], //外部传入存储器
callback: T,
detached?: boolean, // 是否在组件卸载时清除订阅
onCleanup: () => void = noop
) {
subscriptions.push(callback)
// 清除订阅
const removeSubscription = () => {
const idx = subscriptions.indexOf(callback)
if (idx > -1) {
subscriptions.splice(idx, 1)
onCleanup()
}
}
// 如果没传detached,则会自动在组件卸载时清除订阅
if (!detached && getCurrentScope()) {
onScopeDispose(removeSubscription)
}
return removeSubscription
}
export function triggerSubscriptions<T extends _Method>(
subscriptions: T[],
...args: Parameters<T>
) {
subscriptions.slice().forEach((callback) => {
callback(...args)
})
}
基本使用
const unsubscribe = someStore.$onAction(
({
name, // action 名称
store, // store 实例,类似 `someStore`
args, // 传递给 action 的参数数组
after, // 在 action 返回或解决后的钩子
onError, // action 抛出或拒绝的钩子
}) => {
// 这将在执行 "store "的 action 之前触发。
console.log(`xx`)
// 这将在 action 成功并完全运行后触发。它等待着任何返回的 promise
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// 如果 action 抛出或返回一个拒绝的 promise,这将触发
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// 手动删除监听器
unsubscribe()
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> {
// ...
// 对外暴露的store内容
const partialStore = {
_p: pinia,
// _s: scope,
$id,
// 调用$onAction会将回调加入到actionSubscriptions中,调用wrapAction内会触发回调
$onAction: addSubscription.bind(null, actionSubscriptions),
$patch,
$reset,
$subscribe(callback, options = {}) {
// ...
},
$dispose,
} as _StoreWithState<Id, S, G, A>
// ...
}
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> {
// ...
// internal state
let isListening: boolean // 异步监听
let isSyncListening: boolean // 同步监听
let subscriptions: SubscriptionCallback<S>[] = markRaw([]) // 监听state的订阅集合
let actionSubscriptions: StoreOnActionListener<Id, S, G, A>[] = markRaw([]) // 监听action的订阅集合
// ...
// 包装action调用,追加发布订阅功能
function wrapAction(name: string, action: _Method) {
return function (this: any) {
// 根据闭包上下文,调用时设置当前活跃的pinia
setActivePinia(pinia)
const args = Array.from(arguments)
// 调用action后回调集合
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)
}
// 触发action时的回调,同时将after等方法通过参数传入
triggerSubscriptions(actionSubscriptions, {
args,
name,
store,
after,
onError,
})
let ret: any
try {
ret = action.apply(this && this.$id === $id ? this : store, args)
} catch (error) {
// 处理同步错误
triggerSubscriptions(onErrorCallbackList, error)
throw error
}
// 如果是异步方法
if (ret instanceof Promise) {
return ret
.then((value) => {
// 获取到action调用结果后触发
triggerSubscriptions(afterCallbackList, value)
return value
})
.catch((error) => {
triggerSubscriptions(onErrorCallbackList, error)
return Promise.reject(error)
})
}
// allow the afterCallback to override the return value
triggerSubscriptions(afterCallbackList, ret)
return ret
}
}
}
修改state有两种方式,一种通过store.state直接修改然后通过watch API进行监听,另一种通过$patch进行批量修改,通过$patch进行批量修改时,为了只触发一次回调需要手动触发
cartStore.$subscribe((mutation, state) => {
// import { MutationType } from 'pinia'
mutation.type // 'direct' | 'patch object' | 'patch function'
// 和 cartStore.$id 一样
mutation.storeId // 'cart'
// 只有 mutation.type === 'patch object'的情况下才可用
mutation.payload // 传递给 cartStore.$patch() 的补丁对象。
// ...
})
$subscribe监听state直接修改,内部通过watch API实现
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> {
// internal state
let isListening: boolean // set to true at the end
let isSyncListening: boolean // set to true at the end
// ...
// 对外暴露的store内容
const partialStore = {
_p: pinia,
// _s: scope,
$id,
// 调用$onAction会将回调加入到actionSubscriptions中,调用wrapAction内会触发回调
$onAction: addSubscription.bind(null, actionSubscriptions),
$patch,
$reset,
$subscribe(callback, options = {}) {
// 使用者调用后将订阅回调添加进subscriptions
const removeSubscription = addSubscription(
subscriptions,
callback,
options.detached,
() => stopWatcher()
)
// 通过watch监听store.state直接修改
const stopWatcher = scope.run(() =>
watch(
() => pinia.state.value[$id] as UnwrapRef<S>,
(state) => {
// flush默认为'pre',而在调用$patch时isListening会被设置为false,所以不会触发$patch修改state的监听回调
// 但对于store.state直接修改的情况,store在创建完成后isSyncListening和isListening都会变成true,所以能够监听
if (options.flush === 'sync' ? isSyncListening : isListening) {
callback(
{
storeId: $id,
type: MutationType.direct, // type为direct直接修改
events: debuggerEvents as DebuggerEvent,
},
state
)
}
},
assign({}, $subscribeOptions, options)
)
)!
return removeSubscription
},
$dispose,
} as _StoreWithState<Id, S, G, A>
// ...
// 整个store创建结束后将两个监听标识为true,意味着store创建完成,可以进行监听订阅操作
isListening = true
isSyncListening = true
return store
}
$patch用于批量修改state,内部主要通过两个标识位的修改,不触发watch监听回调
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> {
// internal state
let isListening: boolean // set to true at the end
let isSyncListening: boolean // set to true at the end
// ...
function $patch(
partialStateOrMutator:
| _DeepPartial<UnwrapRef<S>>
| ((state: UnwrapRef<S>) => void)
): void {
// 订阅$patch操作type
let subscriptionMutation: SubscriptionCallbackMutation<S>
// 避免批量修改触发$subscribe
isListening = isSyncListening = false
if (__DEV__) {
debuggerEvents = []
}
// 兼容$patch传递函数、对象调用的两种调用方式
if (typeof partialStateOrMutator === 'function') {
partialStateOrMutator(pinia.state.value[$id] as UnwrapRef<S>)
subscriptionMutation = {
type: MutationType.patchFunction, // type类型
storeId: $id,
events: debuggerEvents as DebuggerEvent[],
}
} else {
// $patch传递对象走合并流程
mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator)
subscriptionMutation = {
type: MutationType.patchObject, // type类型
payload: partialStateOrMutator,
storeId: $id,
events: debuggerEvents as DebuggerEvent[],
}
}
const myListenerId = (activeListener = Symbol())
// 对于异步修改情况,异步还原isListening,让$subscribe不会监听通过$patch修改state
nextTick().then(() => {
if (activeListener === myListenerId) {
isListening = true
}
})
isSyncListening = true
// 手动触发订阅,实现通过$patch批量修改state只触发一次订阅回调
triggerSubscriptions(
subscriptions,
subscriptionMutation,
pinia.state.value[$id] as UnwrapRef<S>
)
}
// ...
// 整个store创建结束后将两个监听标识为true,意味着store创建完成,可以进行监听订阅操作
isListening = true
isSyncListening = true
return store
}
对于options store提供的还原初始状态的API
// options store
store.$reset = function $reset() {
const newState = state ? state() : {} // state为options中的state
// 通过$patch批量修改
this.$patch(($state) => {
assign($state, newState)
})
}
// setup store
const $reset = __DEV__ // 开发环境下会报错
? () => {
throw new Error(
`: Store "${$id}" is built using the setup syntax and does not implement $reset().`
)
}
: noop
卸载store的API
// 卸载store
function $dispose() {
scope.stop() //卸载store的scope effect
subscriptions = []
actionSubscriptions = []
pinia._s.delete($id) // 从pinia上删除掉对应的store
}
通过storeToRefs使得解构store也不会丢失响应式
export default defineComponent({
setup() {
const store = useCounterStore()
// ❌ 这将无法生效,因为它破坏了响应性
// 这与从 `props` 中解构是一样的。
const { name, doubleCount } = store
name // "eduardo"
doubleCount // 2
return {
// 始终是 "eduardo"
name,
// 始终是 2
doubleCount,
// 这个将是响应式的
doubleValue: computed(() => store.doubleCount),
}
},
})
// 通过storeToRefs调用
import { storeToRefs } from 'pinia'
export default defineComponent({
setup() {
const store = useCounterStore()
// `name` and `doubleCount` 都是响应式 refs
// 这也将为由插件添加的属性创建 refs
// 同时会跳过任何 action 或非响应式(非 ref/响应式)属性
const { name, doubleCount } = storeToRefs(store)
// 名为 increment 的 action 可以直接提取
const { increment } = store
return {
name,
doubleCount,
increment,
}
},
})
// storeToRefs.ts
export function storeToRefs<SS extends StoreGeneric>(
store: SS
): ToRefs<
StoreState<SS> & StoreGetters<SS> & PiniaCustomStateProperties<StoreState<SS>>
> {
if (isVue2) {
// @ts-expect-error: toRefs include methods and others
return toRefs(store) // vue2 版本直接all in ref
} else {
store = toRaw(store) // 拿到store原始对象
const refs = {} as ToRefs<
StoreState<SS> &
StoreGetters<SS> &
PiniaCustomStateProperties<StoreState<SS>>
>
for (const key in store) {
const value = store[key]
// 只转换ref、reactive属性
if (isRef(value) || isReactive(value)) {
// @ts-expect-error: the key is state or getter
refs[key] =
// ---
toRef(store, key)
}
}
return refs
}
}
基本使用
export default {
computed: {
// 可以访问组件中的 this.count
// 与从 store.count 中读取的数据相同
...mapState(useCounterStore, ['count'])
// 与上述相同,但将其注册为 this.myOwnName
...mapState(useCounterStore, {
myOwnName: 'count',
// 你也可以写一个函数来获得对 store 的访问权
double: store => store.count * 2,
// 它可以访问 `this`,但它没有标注类型...
magicValue(store) {
return store.someGetter + this.count + this.double
},
}),
},
}
mapState
// mapHelpers.ts
export function mapState<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
A
>(
useStore: StoreDefinition<Id, S, G, A>,
keysOrMapper: any
): _MapStateReturn<S, G> | _MapStateObjectReturn<Id, S, G, A> {
// 返回一个对象使得能够放到组件的computed属性上
return Array.isArray(keysOrMapper)
? keysOrMapper.reduce((reduced, key) => {
reduced[key] = function (this: ComponentPublicInstance) {
// 通过useStore,因为store已经创建,所以内部会直接通过pinia._s.get(id)直接返回store而不会重新创建
return useStore(this.$pinia)[key]
} as () => any
return reduced
}, {} as _MapStateReturn<S, G>)
: Object.keys(keysOrMapper).reduce((reduced, key: string) => {
// @ts-expect-error
reduced[key] = function (this: ComponentPublicInstance) {
const store = useStore(this.$pinia)
const storeKey = keysOrMapper[key]
// for some reason TS is unable to infer the type of storeKey to be a
// function
return typeof storeKey === 'function'
? (storeKey as (store: Store<Id, S, G, A>) => any).call(this, store)
: store[storeKey]
}
return reduced
}, {} as _MapStateObjectReturn<Id, S, G, A>)
}
export const mapGetters = mapState //
export function mapActions<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
A,
KeyMapper extends Record<string, keyof A>
>(
useStore: StoreDefinition<Id, S, G, A>,
keysOrMapper: Array<keyof A> | KeyMapper
): _MapActionsReturn<A> | _MapActionsObjectReturn<A, KeyMapper> {
return Array.isArray(keysOrMapper)
? keysOrMapper.reduce((reduced, key) => {
// 和mapState类似,闭包存了下this和其他参数
reduced[key] = function (
this: ComponentPublicInstance,
...args: any[]
) {
return useStore(this.$pinia)[key](...args)
}
return reduced
}, {} as _MapActionsReturn<A>)
: Object.keys(keysOrMapper).reduce((reduced, key: keyof KeyMapper) => {
// @ts-expect-error
reduced[key] = function (
this: ComponentPublicInstance,
...args: any[]
) {
return useStore(this.$pinia)[keysOrMapper[key]](...args)
}
return reduced
}, {} as _MapActionsObjectReturn<A, KeyMapper>)
}