共计 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