关于前端:pinia源码一createPinia源码解析

63次阅读

共计 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 创立一个响应式对象。紧接着申明了两个数组 _ptoBeInstalled,其中_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

这里重点看下 installuse办法。

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
}

你可能有疑难,在 installuse 中都判断了 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。这个对象有两个生命周期函数:beforeCreatedestory

beforeCreate 中设置 this.$pinia,以使vue 实例能够通过 this.$pinia 的形式来获取pinia

正文完
 0