Skip to content

插件

只要通过 pinia.use(myPlugin) 注册过一次插件,之后每一个 store 被初始化(即第一次 useSomeStore())时,Pinia 都会把该插件函数重新执行一遍,并把当前的 store 的 PiniaPluginContext 传入。

因此:

  • 插件会被重复调用多次(store 有多少个就多少次)。
  • 如果只想对某些 store 生效,在插件里自己根据 context.store.$idcontext.options 做判断即可。

创建插件

Pinia 插件是一个函数,可以选择性地返回要添加到 store 的属性。它接收一个可选参数,即 context

ts
// 源码位置:pinia.d.ts
export interface Pinia {
  use(plugin: PiniaPlugin): Pinia
}

export interface PiniaPlugin {
  (context: PiniaPluginContext): void | Partial<Store> | PiniaCustomProperties
}
ts
export interface PiniaPluginContext<
  Id extends string = string,
  S extends StateTree = StateTree,
  G = _GettersTree<S>,
  A = _ActionsTree
> {
  pinia: Pinia// 用 `createPinia()` 创建的 pinia。
  app: App // 用 `createApp()` 创建的当前应用(仅 Vue 3)。
  store: Store<Id, S, G, A>// 该插件想扩展的 store
  options: DefineStoreOptions<Id, S, G, A>// 定义传给 `defineStore()` 的 store 的可选对象。
}

注册插件

然后用 pinia.use() 将这个函数传给 pinia

pinia.use(myPiniaPlugin)

插件只会应用于pinia 传递给应用后创建的 store,否则它们不会生效。

插件是通过 pinia.use() 添加到 pinia 实例的。最简单的例子是通过返回一个对象将一个静态属性添加到所有 store。

ts
import { createPinia } from 'pinia'

// 创建的每个 store 中都会添加一个名为 `secret` 的属性。
function SecretPiniaPlugin() {
  return { secret: 'the cake is a lie' }
}

const pinia = createPinia()
// 将该插件交给 Pinia
pinia.use(SecretPiniaPlugin)

// 在另一个文件中
const store = useStore()
store.secret // 'the cake is a lie'

这对添加全局对象很有用,如路由器、modal 或 toast 管理器。

扩展 Store

你可以直接通过在一个插件中返回包含特定属性的对象来为每个 store 都添加上特定属性:

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

你也可以直接在 store 上设置该属性,但可以的话,请使用返回对象的方法,这样它们就能被 devtools 自动追踪到

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

值得注意的是,每个 store 都被 reactive包装过,所以可以自动解包任何它所包含的 Ref(ref()computed()...)。

在store上直接挂载一个响应式数据,他的使用和state和getter完全一致

ts
const sharedRef = ref('shared')
pinia.use(({ store }) => {
  // 每个 store 都有单独的 `hello` 属性
  store.hello = ref('secret')
  // 它会被自动解包
  store.hello // 'secret'

  // 所有的 store 都在共享 `shared` 属性的值
  store.shared = sharedRef
  store.shared // 'shared'
})

添加新选项

在定义 store 时,可以创建新的选项,以便在插件中使用它们。例如,你可以创建一个 debounce 选项,允许你让任何 action 实现防抖。

ts
defineStore('search', {
  actions: {
    searchContacts() {
      // ...
    },
  },

  // 这将在后面被一个插件读取
  debounce: {
    // 让 action searchContacts 防抖 300ms
    searchContacts: 300,
  },
})

然后,该插件可以读取该选项来包装 action,并替换原始 action:

ts
// 使用任意防抖库
import debounce from 'lodash/debounce'

pinia.use(({ options, store }) => {
  if (options.debounce) {
    // 我们正在用新的 action 来覆盖这些 action
    return Object.keys(options.debounce).reduce((debouncedActions, action) => {
      debouncedActions[action] = debounce(
        store[action],
        options.debounce[action]
      )
      return debouncedActions//同名会被覆盖
    }, {})
  }
})

注意,在使用 setup 语法时,自定义选项作为第 3 个参数传递:

ts
defineStore(
  'search',
  () => {
    // ...
  },
  {
    // 这将在后面被一个插件读取
    debounce: {
      // 让 action searchContacts 防抖 300ms
      searchContacts: 300,
    },
  }
)

为新的store属性添加TS

当在 store 中添加新的属性时,你也应该扩展 PiniaCustomProperties 接口。

ts
import 'pinia'

declare module 'pinia' {
  export interface PiniaCustomProperties {
    // 通过使用一个 setter,我们可以允许字符串和引用。
    set hello(value: string | Ref<string>)
    get hello(): string

    // 你也可以定义更简单的值
    simpleNumber: number

    // 添加路由(#adding-new-external-properties)
    router: Router
  }
}

然后,就可以安全地写入和读取它了:

ts
pinia.use(({ store }) => {
  store.hello = 'Hola'
  store.hello = ref('Hola')

  store.simpleNumber = Math.random()
  // @ts-expect-error: we haven't typed this correctly
  store.simpleNumber = ref(Math.random())
})