共计 3678 个字符,预计需要花费 10 分钟才能阅读完成。
前言
【pinia 源码】系列文章次要剖析 pinia
的实现原理。该系列文章源码参考pinia v2.0.14
。
源码地址:https://github.com/vuejs/pinia
官网文档:https://pinia.vuejs.org
本篇文章将剖析 createPinia
的实现。
应用
通过 createPinia
创立一个 pinia
实例,供应用程序应用。
import {createApp} from 'vue' | |
import {createPinia} from 'pinia' | |
import App from './App.vue' | |
const pinia = createPinia() | |
const app = createApp(App) | |
app.use(pinia).mount('#app') |
createPinia
createPinia
不承受任何参数,它会返回一个 pinia
实例。
源码地位:packages/pinia/src/createPinia.ts
export function createPinia(): Pinia {const scope = effectScope(true) | |
const state = scope.run<Ref<Record<string, StateTree>>>(() => | |
ref<Record<string, StateTree>>({}) | |
)! | |
let _p: Pinia['_p'] = [] | |
let toBeInstalled: PiniaPlugin[] = [] | |
const pinia: Pinia = markRaw({install(app: App) {setActivePinia(pinia) | |
if (!isVue2) { | |
pinia._a = app | |
app.provide(piniaSymbol, pinia) | |
app.config.globalProperties.$pinia = pinia | |
if (__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, | |
_s: new Map<string, StoreGeneric>(), | |
state, | |
}) | |
if (__DEV__ && IS_CLIENT && !__TEST__) {pinia.use(devtoolsPlugin) | |
} | |
return pinia | |
} |
在 createPinia
中首先会创立一个 effect
作用域对象(如果你不理解 effectScope
,可参考:RFC),应用ref
创立一个响应式对象。紧接着申明了两个数组 _p
、toBeInstalled
,其中_p
用来存储扩大 store
的所有插件,toBeInstalled
用来存储那些未 install
之前应用 pinia.use()
增加的的plugin
。
// 创立 effect 作用域 | |
const scope = effectScope(true) | |
// 创立响应式对象 | |
const state = scope.run<Ref<Record<string, StateTree>>>(() => | |
ref<Record<string, StateTree>>({}) | |
)! | |
// 存储扩大 store 的 plugin | |
let _p: Pinia['_p'] = [] | |
// install 之前,应用 pinia.use()增加的 plugin | |
let toBeInstalled: PiniaPlugin[] = [] |
而后申明一个 pinia
对象(这个 pinia
会应用 markRaw
进行包装,使其不会转为proxy
),将其return
。
const pinia: Pinia = markRaw({install(app: App) {// ...}, | |
use(plugin) {// ...}, | |
// 扩大 store 的 plugins | |
_p, | |
// app 实例 | |
_a: null, | |
// effecet 作用域对象 | |
_e: scope, | |
// 在这个 pinia 中注册的 stores | |
_s: new Map<string, StoreGeneric>(), | |
state, | |
}) | |
if (__DEV__ && IS_CLIENT && !__TEST__) {pinia.use(devtoolsPlugin) | |
} | |
return pinia |
这里重点看下 install
和use
办法。
install
当应用 app.use(pinia)
时,触发 pinia.install
函数。在 install
中首先执行了 setActivePinia(pinia)
,它会将pinia
赋给一个 activePinia
的全局变量。
而后会判断是不是 Vue2
环境。如果不是 Vue2
,将app
实例赋给 pinia._a
,而后将pinia
注入到 app
实例,并将 pinia
设置为全局属性 $pinia
。如果此时toBeInstalled
中有 plugins
(在install
之前增加的 plugins
),那么会把这些plugins
增加到 pinia._p
中,增加完之后,置空toBeInstalled
。
install(app: App) {setActivePinia(pinia) | |
if (!isVue2) { | |
pinia._a = app | |
app.provide(piniaSymbol, pinia) | |
app.config.globalProperties.$pinia = pinia | |
if (__DEV__ && IS_CLIENT) {registerPiniaDevtools(app, pinia) | |
} | |
toBeInstalled.forEach((plugin) => _p.push(plugin)) | |
toBeInstalled = []} | |
} |
use
应用 use
办法可增加一个 plugin
以扩大每个 store
。它接管一个plugin
参数,返回以后pinia
。
如果 this._a
是空的,并且不是 Vue2
环境,会将 plugin
中暂存到 toBeInstalled
中,期待 install
时进行装置。否则,间接增加到 this._p
中。
use(plugin) {if (!this._a && !isVue2) {toBeInstalled.push(plugin) | |
} else {_p.push(plugin) | |
} | |
return this | |
} |
你可能有疑难,在 install
、use
中都判断了 Vue2
的状况,难道 pinia
没有解决 Vue2
的状况吗?其实并不是,pinia
提供了 PiniaVuePlugin
专门用来解决 Vue2
的状况。
如果是 Vue2
须要应用如下形式:
Vue.use(PiniaVuePlugin) | |
const pinia = createPinia() | |
new Vue({ | |
el: '#app', | |
pinia, | |
}) |
PiniaVuePlugin
咱们来看下 PiniaVuePlugin
的实现形式。
export const PiniaVuePlugin: Plugin = function (_Vue) { | |
// Equivalent of | |
// app.config.globalProperties.$pinia = pinia | |
_Vue.mixin({beforeCreate() { | |
const options = this.$options | |
if (options.pinia) { | |
const pinia = options.pinia as Pinia | |
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) {setActivePinia(pinia) | |
if (__DEV__) {registerPiniaDevtools(pinia._a, pinia) | |
} | |
} | |
} else if (!this.$pinia && options.parent && options.parent.$pinia) {this.$pinia = options.parent.$pinia} | |
}, | |
destroyed() {delete this._pStores}, | |
}) | |
} |
PiniaVuePlugin
通过向全局混入一个对象来反对pinia
。这个对象有两个生命周期函数:beforeCreate
、destory
。
在 beforeCreate
中设置 this.$pinia
,以使vue
实例能够通过 this.$pinia
的形式来获取pinia