logologo

How to write a pinia plugin 🤔️

Mon Oct 17 2022 8 months ago

plugin-能干嘛 #

  • store添加新属性
  • 定义store时添加新选项
  • store添加新方法
  • 包装现有方法
  • 更改甚至取消操作
  • 实现本地存储等副作用
  • 对特定的store应用

plugin本质 #

pinia的插件本质是一个函数,函数的返回值会混入到store

案例上手:

import { createPinia } from 'pinia'

// 向所有的store混入一个secret属性
function SecretPiniaPlugin() {
  return { secret: 'the cake is a lie' }
}

const pinia = createPinia()
pinia.use(SecretPiniaPlugin)
// 使用 
const store = useStore()
store.secret // 'the cake is a lie'
export interface Pinia {
  install: Exclude<Plugin['install'], undefined>
  state: Ref<Record<string, StateTree>>
  use(plugin: PiniaStorePlugin): Pinia // 使用插件
  _p: Array<PiniaStorePlugin>
  _a: App
  _testing?: boolean
}
use(plugin) {
  if (!localApp) {
    toBeInstalled.push(plugin)
  } else {
    _p.push(plugin)
  }
  return this
}

看一下源码的类型定义,我们可以看到pinia通过use使用插件,使用的插件会存放在实例的_p数组中,并返回this,即当前的实例。

插件的使用时机 #

function buildStoreToUse(
  partialStore: StoreWithState<Id, S, G, A>,
  descriptor: StateDescriptor<S>,
  $id: Id,
  getters: G = {} as G,
  actions: A = {} as A,
  options: DefineStoreOptions<Id, S, G, A>
) {
  ... // 省略部分代码
  // apply all plugins
  pinia._p.forEach((extender) => {
    if (__DEV__ && IS_CLIENT) {
      // @ts-expect-error: conflict between A and ActionsTree
      const extensions = extender({ store, app: pinia._a, pinia, options })
      Object.keys(extensions || {}).forEach((key) =>
        store._customProperties.add(key)
      )
      assign(store, extensions)
    } else {
      // 插件返回的结果混合到 store 上面
      // @ts-expect-error: conflict between A and ActionsTree
      assign(store, extender({ store, app: pinia._a, pinia, options }))
    }
  })

  return store
}

buildStoreToUse函数中我们可以看到插件的应用,而buildStoreToUse恰好也是useStore的最后一步,此时我们可以拿到store的完整所有属性。

我们看到函数对pinia._p的所有插件进行循环,extender即我们use的插件。插件在此刻运行,把{ store, app: pinia._a, pinia, options }作为参数传递给我们的插件,而这个参数就是我们插件拿到的上下文。最后插件的返回结果会通过Object.assign进行混合。

即插件应用成功。此刻我们的store的能力就进行增加。

export function myPiniaPlugin(context) {
  context.pinia // the pinia created with `createPinia()`
  context.app // the current app created with `createApp()` (Vue 3 only)
  context.store // the store the plugin is augmenting
  context.options // the options object defining the store passed to `defineStore()`
  // ...
}

简单的案例 #

我们可以通过简单地在插件中返回它们的对象来向每个商店添加属性:

pinia.use(() => ({ hello: 'world' }))

还可以直接在store上设置属性

pinia.use(({ store }) => {
  store.hello = 'world'
})

插件返回的任何属性都将被 devtools 自动跟踪,因此为了hello在 devtools 中可见,请确保store._customProperties 仅当您想在 devtools 中调试它时才将其添加到dev 模式:

// from the example above
pinia.use(({ store }) => {
  store.hello = 'world'
  // make sure your bundler handle this. webpack and vite should do it by default
  if (process.env.NODE_ENV === 'development') {
    store._customProperties.add('secret')
  }
})
蜀ICP备2022005364号 2022 © Chris