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

33次阅读

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