前言

【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的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

这里重点看下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