前言
【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的pluginlet _p: Pinia['_p'] = []// install之前,应用pinia.use()增加的pluginlet 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