源码笔记"/>
pinia核心源码笔记
pinia 核心源码
记录pinia核心源码阅读笔记,这里跳过hmr(热更新), mapHelpers(class 工具)等工具源码。 剔除的部分vue2.0兼容代码。 当前pinia版本2.0.13
执行流程概述
- 创建pinia实例,挂载到vue
- 定义state
- 创建组件
- 调用useState
- 生成并缓存pinia
- 注销组件
- 注销监听
rootStore.js
这里主要提供 activePinia(当前可用pinia实例)缓存对象。 并提供两个操作方法,
- setActivePinia 更新 activePinia
export const setActivePinia = (pinia: Pinia | undefined) =>(activePinia = pinia)
- getActivePinia 获取 activePinia
export const getActivePinia = () =>// 这里优先返回全局注册的pinia实例(getCurrentInstance() && inject(piniaSymbol)) || activePinia
subscriptions.ts
响应事件相关, 提供两个方法
- addSubscription
// 向当前state事件队列中注册事件回调
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()}}// 默认组件注销时,清理事件回调if (!detached && getCurrentInstance()) {onUnmounted(removeSubscription)}return removeSubscription
}
- triggerSubscriptions
// 执行事件队列
export function triggerSubscriptions<T extends _Method>( subscriptions: T[],...args: Parameters<T> ) {subscriptions.slice().forEach((callback) => {callback(...args)})
}
createPinia.ts
创建pinia实例
export function createPinia(): Pinia {// 创建响应式空间,空值pinia相关的响应对象的有效性const scope = effectScope(true)// state缓存空间, 生成的store将缓存到该队列中// 当使用useState是,将通过注册的id,从stateTrue// 中查询对应的store,保证不同组件使用相同的storeconst state = scope.run<Ref<Record<string, StateTree>>>(() =>ref<Record<string, StateTree>>({}))!// 插件队列let _p: Pinia['_p'] = []let toBeInstalled: PiniaPlugin[] = []// 创建pinia实例// markRaw 保证pinia不会被代理const pinia: Pinia = markRaw({// 将pinia实例注册到Vue实例中install(app: App) {// 激活当前pinia实例, setActivePinia(pinia)if (!isVue2) {// 设置vue实例pinia._a = app// 通过依赖注入设置全局默认pinia实例// 后面useState会用到app.provide(piniaSymbol, pinia)// 挂载全局pinia实例app.config.globalProperties.$pinia = piniaif (__DEV__ && IS_CLIENT) {registerPiniaDevtools(app, pinia)}// 添加插件toBeInstalled.forEach((plugin) => _p.push(plugin))toBeInstalled = []}},// 注册插件use(plugin) {if (!this._a && !isVue2) {toBeInstalled.push(plugin)} else {_p.push(plugin)}return this},// 插件集合_p,// 应用实例_a: null,// 响应空间_e: scope,// store 队列_s: new Map<string, StoreGeneric>(),// state配置队列, 用于重置statestate,})return pinia
}
这里主要创建pinia实例,如果pinia实例被注册要vue应用实例时,将执行一些初始值设置,依赖注册pinia实例,以供useState使用
store.ts
pinia状态, 主要包括三个核心
- defineStore 定义状态
- createOptionsStore 对象型状态生成函数
defineStore(id, {state, getter, action})
- createSetupStore 函数型状态生成函数
defineStore(id, () => { setup(){} })
defineStore 定义store
defineStore 只做了两件事
- 参数处理
- 构建useState函数
这里主要看useState做了什么
// 通过配置类型判断配置类型
const isSetupStore = typeof setup === 'function'
...// useState 可接收一个pinia实例作为参数
// 如果设置参数pinia,将通过依赖注入获取全局默认pinia实例
pinia =(__TEST__ && activePinia && activePinia._testing ? null : pinia) ||(currentInstance && inject(piniaSymbol))// 激活当前pinia实例
if (pinia) setActivePinia(pinia)// 通过 id查询对应的store是否已经创建
if (!pinia._s.has(id)) {// 如果未在当前pinia上查到对应store, 将根据参数类型创建storeif (isSetupStore) {// 函数型createSetupStore(id, setup, options, pinia)} else {// 对象型createOptionsStore(id, options as any, pinia)}...}// 如果store存在返回该实例
const store: StoreGeneric = pinia._s.get(id)!
...
return store as any// 其他
// 在创建useStore函数后
// 将当前id挂载useStore.$id属性上
useStore.$id = id
createOptionsStore 对象型store生成
这个函数其实是createSetupStore的包装函数, 将对象型的定义转为函数型 再交由createOptionsStore生成store
store生成
// 这里会先将options转为setup函数
// 通过createSetupStore生成store实例
store = createSetupStore(id, setup, options, pinia, hot)// 绑定重置函数
store.$reset = function $reset() {// state 这里是闭包const newState = state ? state() : {}// this指向store// this.$patch 是state更新函数, this.$patch(($state) => {// 将原state与现有state合并,将state部分属性值重置assign($state, newState)})
}
setup函数
这里看setup函数做了什么
function setup() {...// 初始将state缓存到当前pinia.state中
pinia.state.value[id] = state ? state() : {}// 将state转未ref
const localState = toRefs(pinia.state.value[id])// 返回响应对象return assign(localState, // state => Refs(state)actions, // actions => actions// 遍历getters, 将属性包裹一层computedObject.keys(getters || {}).reduce((computedGetters, name) => {// markRow 防止对象被重复代理computedGetters[name] = markRaw(computed(() => {// pinia 处于闭包setActivePinia(pinia)// it was created just beforeconst store = pinia._s.get(id)!// 将执行函数绑定在store上下文中,支持 {getters: { fn(){ this.count++ } }} 模式// 所以当使用箭头函数时不能使用this获取state// 函数接收state作为参数, 支持{gtters: { f(state){state.count++ } }}// 返回getter执行结果return getters![name].call(store, store)}))return computedGetters}{})
}
所以setup主要作用是 1.将getter包裹computed, 2.返回新的store定义,通过getter的包装过程,知道了为什么箭头函数不能使用this模式,主要应为箭头函数的this原定义上下文绑定,后期无法通过call函数绑定到state上。
createSetupStore 函数型store生成
生成并挂载store实例
公共变量
let isListening: boolean // 监听函数执行时机标识
let isSyncListening: boolean // 监听函数执行时机标识
// state 更新响应队列,缓存¥subscribe挂载的任务
let subscriptions: SubscriptionCallback<S>[] = markRaw([])
// actions 响应事件队列, 缓存$onAction挂载的任务
let actionSubscriptions: StoreOnActionListener<Id, S, G, A>[] = markRaw([])
// debugger 事件队列
let debuggerEvents: DebuggerEvent[] | DebuggerEvent
// 初始缓存state
const initialState = pinia.state.value[$id] as UnwrapRef<S> | undefined
store 实例
因为createSetupStore的主要功能就是生成store实例,所以这里先看生成的store 主要步骤
// 如果state不存在,设置默认值if (!buildState && !initialState && (!__DEV__ || !hot)) {pinia.state.value[$id] = {}}// store基础方法属性
// 这里主要定义store实力的操作API
const partialStore = {_p: pinia,// action 响应事件注册函数$onAction: addSubscription.bind(null, actionSubscriptions),// state 更新函数$patch,// 重置store$reset,
// 注册响应修改监听$subscribe(callback, options = {}) {...},// 注销store$dispose,
}// 转为响应对象
const store: Store<Id, S, G, A> = reactive(assign({}, partialStore)
)// 缓存store, useState通过当前激活的pinia获取到store
pinia._s.set($id, store)// 合并store
// setupStore为setup()执行处理后配置对象
// 主要是对action的包装以及部分属性的合并
assign(store, setupStore)
// 这里为了 storeToRefs, 将响应属性合并到store原对象上
// storeToRefs 将先取得toRaw(store)再说Refs处理
assign(toRaw(store), setupStore)// 绑定$state属性
Object.defineProperty(store, '$state', {get: () => pinia.state.value[$id],set: (state) => {$patch(($state) => {assign($state, state)})},
})
这里剔除的具体的方法定义,和周期函数的调用,主要看store的基础生成。
$patch state更新
function $patch( partialStateOrMutator:| _DeepPartial<UnwrapRef<S>>| ((state: UnwrapRef<S>) => void) ): void {let subscriptionMutation: SubscriptionCallbackMutation<S>// 阻止$subscribe监听事件执行// 防止重复触发// 保证$subscribe在完整合并后再执行isListening = isSyncListening = falseif (__DEV__) {debuggerEvents = []}// 如果状态修改器为函数,执行并生成修改类型if (typeof partialStateOrMutator === 'function') {// 例如 $reset()partialStateOrMutator(pinia.state.value[$id] as UnwrapRef<S>)// 函数更新类型subscriptionMutation = {type: MutationType.patchFunction,storeId: $id,events: debuggerEvents as DebuggerEvent[],}} else {// 如果状态修改器为对象, 合并到新state中// mergeReactiveObjects将递归合并对象内的属性mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator)// 对象更新类型subscriptionMutation = {type: MutationType.patchObject,payload: partialStateOrMutator,storeId: $id,events: debuggerEvents as DebuggerEvent[],}}// 开启监听锁nextTick().then(() => {isListening = true})isSyncListening = true// 应为之前关闭了watch监听, 所以这里需要手动执行一次监听队列triggerSubscriptions(subscriptions,subscriptionMutation,pinia.state.value[$id] as UnwrapRef<S>)}
patch用来更新state值,并出发更新监听,patch用来更新state值, 并出发更新监听, patch用来更新state值,并出发更新监听,subscribe绑定的事件将在state更新后被执行一次
$subscribe 更新监听
$subscribe(callback, options = {}) {// 向任务队列中添加任务, 并返回移除函数const removeSubscription = addSubscription(subscriptions,callback,options.detached,// 这里有个问题 stopWatcher 先于定义,const应该存在假死区 () => stopWatcher())// 挂载更新监听 const stopWatcher = scope.run(() =>watch(() => pinia.state.value[$id] as UnwrapRef<S>,(state) => {// 更新锁, patch时禁用更新监听if (options.flush === 'sync' ? isSyncListening : isListening) {callback({storeId: $id,type: MutationType.direct,events: debuggerEvents as DebuggerEvent,},state)}},assign({}, $subscribeOptions, options)))!return removeSubscription}
wrapAction
action 包装函数,主要为了提供 $onAction 监听钩子, 该函数在setupStore生成时被调用
function wrapAction(name: string, action: _Method) {return function (this: any) {setActivePinia(pinia)const args = Array.from(arguments)// action执行后回调队列const afterCallbackList: Array<(resolvedReturn: any) => any> = []// 错误回调队列const onErrorCallbackList: Array<(error: unknown) => unknown> = []// action执行后回调添加函数function after(callback: _ArrayType<typeof afterCallbackList>) {afterCallbackList.push(callback)}// 错误回调添加函数function onError(callback: _ArrayType<typeof onErrorCallbackList>) {onErrorCallbackList.push(callback)}// 执行action任务队列triggerSubscriptions(actionSubscriptions, {args,name,store,after,onError,})let ret: anytry {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) => {triggerSubscriptions(afterCallbackList, value)return value}).catch((error) => {triggerSubscriptions(onErrorCallbackList, error)return Promise.reject(error)})}triggerSubscriptions(afterCallbackList, ret)return ret}}
执行流程 $onAction监听队列 -> action -> after任务队列 or error任务队列 应为onAction本身可以看作 beforeCallbackList, action的前置监听队列
其他钩子
- plugins
// 生成store后将执行插件函数
pinia._p.forEach((extender) => {...}
- hydrate
// 执行plugins后执行合并函数
(options as DefineStoreOptions<Id, S, G, A>).hydrate!(store.$state,initialState
)
总结
pinia核心代码并不多,主要功能放在了store生成,钩子包装。 值得注意的是:
- pinia实例的调用
- scope 空值响应作用空间
- 钩子的调度
- 兼容支持
疑问
- $subscribe 监听中 stopWatcher 变量先于定义
const removeSubscription = addSubscription(....() => stopWatcher()
)
const stopWatcher = scope.run(() =>{...})
- 部分属性遍历上是否可以用其他的方法
// 使用了 for in 遍历,将获取到原型上方法
for (const key in patchToApply) {if (!patchToApply.hasOwnProperty(key)) continueconst subPatch = patchToApply[key]const targetValue = target[key]
更多推荐
pinia核心源码笔记
发布评论