前言
【pinia源码】系列文章主要分析pinia
的实现原理。该系列文章源码参考pinia v2.0.14
。
源码地址:https://github.com/vuejs/pinia
本篇文章将分析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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。