rootStore.ts
Pinia 实例
-
install: (app: App) => void:- 当你调用
app.use(pinia)时,实际上就是调用这个install函数,它会执行一些必要的初始化操作,例如设置全局 Pinia 实例、将 Pinia 注入到依赖注入上下文等。
- 当你调用
-
state: Ref<Record<string, StateTree>>:- 存储 Pinia 实例中的所有 store 的根状态。它是一个响应式对象,key 是 store 的 id(字符串),value 是 store 的状态(
StateTree,通常是一个普通 JavaScript 对象)。
- 存储 Pinia 实例中的所有 store 的根状态。它是一个响应式对象,key 是 store 的 id(字符串),value 是 store 的状态(
-
use(plugin: PiniaPlugin): Pinia:- Pinia 插件可以扩展 store 的功能,例如添加持久化、devtools 集成等。
use方法提供了一种灵活的方式来扩展 Pinia 的功能。
- Pinia 插件可以扩展 store 的功能,例如添加持久化、devtools 集成等。
-
_p: PiniaPlugin[]:- 作用: 存储所有已注册的 Pinia 插件。
- 意义: 用于跟踪所有已安装的插件,在创建 store 的时候会被调用,以便应用这些插件。
-
_a: App:- 作用: 存储与 Pinia 实例关联的 Vue 应用实例。
- 意义: Pinia 需要访问 Vue 应用实例来执行一些操作,例如注册 devtools 插件。
-
_e: EffectScope:- 作用: Pinia 实例的 effect scope。Effect Scope可以用来控制一组副作用 effect (例如 computed、watch) 的激活时机。
- 意义: 用于管理 Pinia 实例的副作用。
-
_s: Map<string, StoreGeneric>:- 存储所有已创建的 store。Key 是 store 的 id(字符串),value 是 store 实例(
StoreGeneric,一个通用的 store 类型)。 - StoreGeneric
export type StoreGeneric = Store< string, StateTree, _GettersTree<StateTree>, _ActionsTree > - 存储所有已创建的 store。Key 是 store 的 id(字符串),value 是 store 实例(
-
_testing?: boolean:- 用于在测试环境中绕过
useStore(pinia)。
- 用于在测试环境中绕过
activePinia
全局变量,记录当前pinia实例
export let activePinia: Pinia | undefined
PiniaPluginContext And PiniaPlugin 不做研究
export interface PiniaPlugin {
/**
* Plugin to extend every store. Returns an object to extend the store or
* nothing.
*
* @param context - Context
*/
(
context: PiniaPluginContext
): Partial<PiniaCustomProperties & PiniaCustomStateProperties> | void
}
createPinia.ts
createPinia(): Pinia
- 声明一个EffectScope,使用run方法传入一个efect,返回一个state
- 创建一个Pinia实例,使用markRaw()去除响应式,最后return 这个pinia 实例
export function createPinia(): Pinia {
const scope = effectScope(true)
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({})
)!
// ... 省略插件执行
const pinia: = markRaw({
install(app: App) {
// 作用: 设置当前激活的 Pinia 实例。
// 意义:全局变量,记录当前pinia实例
// 代码:export const setActivePinia: _SetActivePinia = (pinia) => (activePinia = pinia)
setActivePinia(pinia)
// 将 Vue 应用实例 (app) 赋值给 Pinia 实例的 _a 属性。
// Pinia 需要访问 Vue 应用实例,来注册devtools
pinia._a = app
// 作用: 使用 Vue 的 provide 功能,将 Pinia 实例注入到 Vue 应用的依赖注入上下文中。
// 意义: 让所有的组件都可以用 `inject(piniaSymbol)`来获取pinia实例
app.provide(piniaSymbol, pinia)
// 作用: 将 Pinia 实例添加到 Vue 应用的全局属性中,通过 $pinia 访问 Pinia 实例
app.config.globalProperties.$pinia = pinia
},
// ... 省略use 就是添加插件
})
}
disposePinia(pinia: Pinia)
销毁一个pinia实例,并且删除他的EffectScope state,plugins和store,销毁后不能再次使用
export function disposePinia(pinia: Pinia) {
pinia._e.stop()
pinia._s.clear()
pinia._p.splice(0)
pinia.state.value = {}
// @ts-expect-error: non valid
pinia._a = null
}
store.ts
createSetupStore() 重点!!!
负责创建和初始化 Pinia store 实例
- 创建响应式state
- 处理 actions
- 应用插件
- 设置HMR
- 与devtools集成
函数签名和泛型
function createSetupStore<
Id extends string, // storeId
SS extends Record<any, unknown>, // setup 函数
S extends StateTree, // state
G extends Record<string, _Method>, // getter
A extends _ActionsTree, // action
>(
$id: Id,
setup: (helpers: SetupStoreHelpers) => SS,
options:
| DefineSetupStoreOptions<Id, S, G, A>
| DefineStoreOptions<Id, S, G, A> = {},// 配置 store 的选项。可以包含 state 的初始值、getters、actions 等
pinia: Pinia,
hot?: boolean,// 是否启用热模块替换(hmr)
isOptionsStore?: boolean //是否是 Options API 形式的store
)
内部变量和初始化
// 一个 effect scope,用于管理 store 的副作用
let scope!: EffectScope
// 合并了 actions 的选项对象,用于插件。
const optionsForPlugin: DefineStoreOptionsInPlugin<Id, S, G, A> = assign(
{ actions: {} as A },
options
)
/* 开发环境下的检查: 确保 pinia 实例没有被销毁。 */
if (__DEV__ && !pinia._e.active) {
throw new Error('Pinia destroyed')
}
const $subscribeOptions: WatchOptions = { deep: true }
// 内部状态
let isListening: boolean // 异步监听 state 的变化
let isSyncListening: boolean SubscriptionCallback<S>[] = [] // 同步监听
let actionSubscriptions: StoreOnActionListener<Id, S, G, A>[] = [] // 存储 action 变化的订阅回调函数
let debuggerEvents: DebuggerEvent[] | DebuggerEvent // 存储调试事件
const initialState = pinia.state.value[$id] as UnwrapRef<S> | undefined // 从 Pinia 实例的 state 中获取 store 的初始状态
初始化Pinia的state
// 如果不是 options store,并且 Pinia 实例中没有初始状态,则初始化 Pinia 实例中的 state。
if (!isOptionsStore && !initialState && (!__DEV__ || !hot)) {
/* istanbul ignore if */
pinia.state.value[$id] = {}
}
// 用于存储热模块替换时的 state。
const hotState = ref({} as S)
$patch 方法
// 允许你通过部分 state 对象或 mutation 函数来批量更新 store 的 state。
function $patch( partialStateOrMutator: | _DeepPartial<UnwrapRef<S>> | ((state: UnwrapRef<S>) => void) ): void {
let subscriptionMutation: SubscriptionCallbackMutation<S>
// 在更新 state 之前,会暂停监听 state 的变化,以避免触发不必要的回调
isListening = isSyncListening = false
if (typeof partialStateOrMutator === 'function') {
// 一个函数,接收 store 的 state 作为参数,并允许你直接修改 state
partialStateOrMutator(pinia.state.value[$id] as UnwrapRef<S>)
subscriptionMutation = {
type: MutationType.patchFunction,
storeId: $id,
events: debuggerEvents as DebuggerEvent[],
}
} else {
// 一个 state 对象,会被合并到 store 的 state 中。
// 使用 `mergeReactiveObjects` 来合并 state,保持响应性
mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator)
subscriptionMutation = {
type: MutationType.patchObject,
payload: partialStateOrMutator,
storeId: $id,
events: debuggerEvents as DebuggerEvent[],
}
}
const myListenerId = (activeListener = Symbol())
// 更新 state 之后 开启state监听
nextTick().then(() => {
if (activeListener === myListenerId) {
isListening = true
}
})
isSyncListening = true
// 手动触发订阅回调
triggerSubscriptions(
subscriptions,
subscriptionMutation,
pinia.state.value[$id] as UnwrapRef<S>
)
}
$reset
$reset: 用于将 store 的 state 重置为初始状态。
- 对于 Options API 形式的store,它会调用
state函数来获取初始 state,并使用$patch方法来更新 state。 - 对于 Setup API 形式的 store,开发环境下会抛出一个错误,因为你需要自己实现
$reset方法。
const $reset = isOptionsStore
? function $reset(this: _StoreWithState<Id, S, G, A>) {
const { state } = options as DefineStoreOptions<Id, S, G, A>
const newState: _DeepPartial<UnwrapRef<S>> = state ? state() : {}
// we use a patch to group all changes into one single subscription
this.$patch(($state) => {
// @ts-expect-error: FIXME: shouldn't error?
assign($state, newState)
})
}
: /* istanbul ignore next */
__DEV__
? () => {
throw new Error(
`🍍: Store "${$id}" is built using the setup syntax and does not implement $reset().`
)
}
: noop
$dispose 方法
$dispose: 用于注销 Store,停止 effect scope,移除所有的订阅,并且从 pinia 实例中移除该store。
function $dispose() {
scope.stop()
subscriptions = []
actionSubscriptions = []
pinia._s.delete($id)
}
getter 定义
Pinia 的 getters 的定义比较分散,并没有一个单独的源码文件来专门定义 getters 的创建过程。 它们主要在以下几个地方被定义和处理:
-
defineStore函数 (主要定义的地方):defineStore函数是定义 store 的入口,无论是 options API 还是 setup API,最终都会通过它来创建 store。- 在
defineStore函数中(特别是 options API 的 store),你可以定义getters选项。 这些 getters 会被处理并添加到 store 实例中。
-
createSetupStore函数 (setup API 的 store):- 当我们使用
setupAPI 定义 store 时,getter 的定义实际上是在setup函数内部完成的,你可以使用computed函数来创建 getter。 - 在
createSetupStore函数中,会遍历setup函数的返回值,如果某个属性是computed,则会将其识别为 getter,并进行相应的处理(例如,添加到_hmrPayload.getters中,以便在 devtools 中显示)。
- 当我们使用
-
storeToRefs函数:storeToRefs函数用于将 store 的 state 和 getters 转换为 refs,以便在组件中使用。- 这个函数会遍历 store 的所有属性,如果是 ref 或者 computed,则会将其转换为 ref,以便在组件中进行响应式地访问。
action 函数
action: 一个高阶函数,用于包装 action 函数,以便能够追踪 action 的调用和状态。- 它接收一个 action 函数
fn和 action 的名称name作为参数。 - 返回一个新的函数,该函数在调用原始 action 函数之前和之后,会触发相应的回调函数。
- 这个函数允许你使用
$onAction方法来订阅 action 的调用。
const ACTION_MARKER = Symbol('pinia action'); // 一个唯一的 Symbol,用于标记一个 action 是否被 Pinia 包装过。
const ACTION_NAME = Symbol('action name'); // 一个唯一的 Symbol,用于存储 action 的名称。
type _Method = (...args: any[]) => any;
type _ArrayType<T extends any[]> = T extends (infer U)[] ? U : never;
interface MarkedAction<Fn extends _Method> extends Fn {
[ACTION_MARKER]: true;
[ACTION_NAME]: string;
}
const action = <Fn extends _Method>(fn: Fn, name: string = ''): Fn => {
// 如果函数已经被标记为 action,只需要更新它的名称。
if (ACTION_MARKER in fn) {
// 确保返回的函数设置了名称。这在 action 被重新定义并赋予新名称时非常重要。
(fn as unknown as MarkedAction<Fn>)[ACTION_NAME] = name;
return fn;
}
// 创建一个新的包装函数,它拦截原始函数的执行。
const wrappedAction = function (this: any) {
// 确保 Pinia 实例在 action 的执行上下文中处于激活状态。
setActivePinia(pinia);
const args = Array.from(arguments);
// 初始化数组以保存 after 和 error 回调。
const afterCallbackList: Array<(resolvedReturn: any) => any> = [];
const onErrorCallbackList: Array<(error: unknown) => unknown> = [];
/**
* 注册一个回调函数,在 action 成功完成后执行。
*
* @param callback - action 完成后要调用的函数。 它接收 action 的解析返回值作为参数。
*/
function after(callback: _ArrayType<typeof afterCallbackList>) {
afterCallbackList.push(callback);
}
/**
* 注册一个回调函数,在 action 抛出错误时执行。
*
* @param callback - action 发生错误时要调用的函数。 它接收错误作为参数。
*/
function onError(callback: _ArrayType<typeof onErrorCallbackList>) {
onErrorCallbackList.push(callback);
}
// 在 action 执行之前触发订阅。 这允许订阅者 hook 到 action 的生命周期中。
// @ts-expect-error: Typescript 不知道 triggerSubscriptions 的类型
triggerSubscriptions(actionSubscriptions, {
args, // 传递给 action 的参数。
name: wrappedAction[ACTION_NAME], // action 的名称。
store, // Pinia store 实例。
after, // 注册 action 后回调的函数。
onError, // 注册错误回调的函数。
});
let ret: unknown;
try {
// 执行原始的 action 函数。
// 上下文 (`this`) 被绑定到 store 实例,确保可以正确访问 store 属性。
// 如果此上下文中的函数且 _id 与 store 的相同,则使用该上下文,否则使用 store
ret = fn.apply(this && this.$id === $id ? this : store, args);
} catch (error) {
// 如果 action 抛出错误,则触发错误订阅并重新抛出错误。
triggerSubscriptions(onErrorCallbackList, error);
throw error; // 重新抛出错误以将其传播给调用者。
}
// 处理 Promise 返回值(异步 action)。
if (ret instanceof Promise) {
return ret
.then((value) => {
// 如果 Promise 成功解析,则触发 after 回调。
triggerSubscriptions(afterCallbackList, value);
return value; // 返回已解析的值。
})
.catch((error) => {
// 如果 Promise 被拒绝,则触发错误回调并返回一个被拒绝的 Promise。
triggerSubscriptions(onErrorCallbackList, error);
return Promise.reject(error); // 返回一个被拒绝的 Promise 以传播错误。
});
}
// 触发同步 action 的 after 回调。
triggerSubscriptions(afterCallbackList, ret);
return ret; // 返回同步 action 的结果。
} as MarkedAction<Fn>;
// 将包装函数标记为一个 action。
wrappedAction[ACTION_MARKER] = true;
// 设置包装 action 的名称。 名称可能会稍后提供。
wrappedAction[ACTION_NAME] = name;
// 返回包装的 action,但将类型限制为仅 Fn。
// 这是因为添加的属性 (ACTION_MARKER、ACTION_NAME) 是内部的,仅通过 `$onAction()` 公开。
// @ts-expect-error: 我们有意将返回类型限制为仅 Fn
// 因为所有添加的属性都是内部属性,仅通过 `$onAction()` 公开
return wrappedAction;
};