Plugins

由于低版本的APIPiniastores可以完全扩展。下面是一些你可以做的事情:

  • stores添加新的属性
  • 在定义stores时添加新选项
  • stores添加新方法
  • 包装现有的方法
  • 更改甚至取消操作
  • 实现像本地存储这样的功能
  • 只适用于特定的stores

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

  1. import { createPinia } from 'pinia'
  2. // add a property named `secret` to every store that is created after this plugin is installed
  3. // this could be in a different file
  4. function SecretPiniaPlugin() {
  5. return { secret: 'the cake is a lie' }
  6. }
  7. const pinia = createPinia()
  8. // give the plugin to pinia
  9. pinia.use(SecretPiniaPlugin)
  10. // in another file
  11. const store = useStore()
  12. store.secret // 'the cake is a lie'

这对于添加全局对象(如routermodaltoast管理器)非常有用。

介绍

Pinia的插件是一个函数,可以选择返回要添加到store中的属性。它有一个可选参数 context:

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

然后将此函数传递给piniapinia.use()

  1. pinia.use(myPiniaPlugin)

插件只应用于stores被创建在pinia传递给应用程序后 ,否则它们不会被应用。

扩展 Store

你可以通过在插件中返回一个属性对象来为每个store添加属性:

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

你也可以直接在store中设置属性,如果可以的话,请返回版本,以便它们可以被devtools自动跟踪:

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

插件返回的任何属性都将由devtools自动追踪,因此为了hellodevtools中可见,请确保仅在开发模式中添加store._customProperties属性,如果您想在devtools中调试的话:

  1. // from the example above
  2. pinia.use(({ store }) => {
  3. store.hello = 'world'
  4. // make sure your bundler handle this. webpack and vite should do it by default
  5. if (process.env.NODE_ENV === 'development') {
  6. // add any keys you set on the store
  7. store._customProperties.add('hello')
  8. }
  9. })

需要注意的是,每个store都会使用reactive包装,并且会自动解包它包含的任何Ref(ref(), computed(), …)等:

  1. const sharedRef = ref('shared')
  2. pinia.use(({ store }) => {
  3. // each store has its individual `hello` property
  4. store.hello = ref('secret')
  5. // it gets automatically unwrapped
  6. store.hello // 'secret'
  7. // all stores are sharing the value `shared` property
  8. store.shared = sharedRef
  9. store.shared // 'shared'
  10. })

这就是为什么您可以访问所有不带.value计算属性它们是响应式的原因。

添加新状态

如果您想在激活过程中添加新的状态属性或属性到store,您必须在两个地方添加它:

  • store中,您可以通过store.myState访问它
  • store.$state中,它可以在devtools中使用,并且在SSR期间被序列化。

请注意,这允许您共享refcomputed属性:

  1. const globalSecret = ref('secret')
  2. pinia.use(({ store }) => {
  3. // `secret` is shared among all stores
  4. store.$state.secret = globalSecret
  5. store.secret = globalSecret
  6. // it gets automatically unwrapped
  7. store.secret // 'secret'
  8. const hasError = ref(false)
  9. store.$state.hasError = hasError
  10. // this one must always be set
  11. store.hasError = toRef(store.$state, 'hasError')
  12. // in this case it's better not to return `hasError` since it
  13. // will be displayed in the `state` section in the devtools
  14. // anyway and if we return it, devtools will display it twice.
  15. })

请注意,在插件中发生的状态改变或添加(包括调用store.$patch())发生在store激活之前,因此不会触发任何订阅。

WARNING 如果您使用的是Vue 2Pinia将受到与Vue相同的反应警告。当创建新的状态属性如 secrethasError时,您需要使用来自@vue/composition-apiset方法。

  1. import { set } from '@vue/composition-api'
  2. pinia.use(({ store }) => {
  3. if (!store.$state.hasOwnProperty('hello')) {
  4. const secretRef = ref('secret')
  5. // If the data is meant to be used during SSR, you should
  6. // set it on the `$state` property so it is serialized and
  7. // picked up during hydration
  8. set(store.$state, 'secret', secretRef)
  9. // set it directly on the store too so you can access it
  10. // both ways: `store.$state.secret` / `store.secret`
  11. set(store, 'secret', secretRef)
  12. store.secret // 'secret'
  13. }
  14. })

添加新的外部属性

当添加外部属性,来自其他库的类实例或简单的非响应式对象时,应该在将对象传递给pinia 之前使用markRaw()包装该对象。下面是一个将路由添加到所有store的示例:

  1. import { markRaw } from 'vue'
  2. // adapt this based on where your router is
  3. import { router } from './router'
  4. pinia.use(({ store }) => {
  5. store.router = markRaw(router)
  6. })

在插件内部调用 $subscribe

您也可以在插件中使用store.$subscribestore.$onAction:

  1. pinia.use(({ store }) => {
  2. store.$subscribe(() => {
  3. // react to store changes
  4. })
  5. store.$onAction(() => {
  6. // react to store actions
  7. })
  8. })

添加新选项

可以在定义stores时创建新的选项,以便随后从插件中使用它们。例如,你可以创建一个debounce选项,允许你对任何操作进行debounce :

  1. defineStore('search', {
  2. actions: {
  3. searchContacts() {
  4. // ...
  5. },
  6. },
  7. // this will be read by a plugin later on
  8. debounce: {
  9. // debounce the action searchContacts by 300ms
  10. searchContacts: 300,
  11. },
  12. })

插件可以读取该选项来包装actions并替换原来的actions:

  1. // use any debounce library
  2. import debounce from 'lodash/debunce'
  3. pinia.use(({ options, store }) => {
  4. if (options.debounce) {
  5. // we are overriding the actions with new ones
  6. return Object.keys(options.debounce).reduce((debouncedActions, action) => {
  7. debouncedActions[action] = debounce(
  8. store[action],
  9. options.debounce[action]
  10. )
  11. return debouncedActions
  12. }, {})
  13. }
  14. })

请注意,使用setup语法时,自定义选项作为第三个参数传入:

  1. defineStore(
  2. 'search',
  3. () => {
  4. // ...
  5. },
  6. {
  7. // this will be read by a plugin later on
  8. debounce: {
  9. // debounce the action searchContacts by 300ms
  10. searchContacts: 300,
  11. },
  12. }
  13. )

TypeScript

上面显示的所有内容都可以通过编写支持,因此您无需使用any@ts-ignore

编写插件

Pinia插件可以按如下方式编写:

  1. import { PiniaPluginContext } from 'pinia'
  2. export function myPiniaPlugin(context: PiniaPluginContext) {
  3. // ...
  4. }

编写新的store属性

当向stores添加新属性时,您还应该扩展PiniaCustomProperties接口。

  1. import 'pinia'
  2. declare module 'pinia' {
  3. export interface PiniaCustomProperties {
  4. // by using a setter we can allow both strings and refs
  5. set hello(value: string | Ref<string>)
  6. get hello(): string
  7. // you can define simpler values too
  8. simpleNumber: number
  9. }
  10. }

然后可以安全地写入和读取:

  1. pinia.use(({ store }) => {
  2. store.hello = 'Hola'
  3. store.hello = ref('Hola')
  4. store.number = Math.random()
  5. // @ts-expect-error: we haven't typed this correctly
  6. store.number = ref(Math.random())
  7. })

PiniaCustomProperties是一个泛型类型,允许您引用store的属性。想象一下下面的示例,我们将初始选项复制为$options(这仅适用于option stores):

  1. pinia.use(({ options }) => ({ $options: options }))

我们可以通过使用PiniaCustomProperties的4个泛型类型来正确地输入这个值:

  1. import 'pinia'
  2. declare module 'pinia' {
  3. export interface PiniaCustomProperties<Id, S, G, A> {
  4. $options: {
  5. id: Id
  6. state?: () => S
  7. getters?: G
  8. actions?: A
  9. }
  10. }
  11. }

TIP

在泛型中扩展类型时,它们的命名必须与源码中的完全相同。Id不能命名为idIS也不能命名为State。以下是每个字母所代表的含义:

  • S: State
  • G: Getters
  • A: Actions
  • SS: Setup Store / Store

编写新的状态

当添加新的状态属性时(同时添加到storestore.$state),您需要将类型添加到PiniaCustomStateProperties。与PiniaCustomProperties不同的是,它只接收State泛型:

  1. import 'pinia'
  2. declare module 'pinia' {
  3. export interface PiniaCustomStateProperties<S> {
  4. hello: string
  5. }
  6. }

编写新的创建选项

当为defineStore()创建新选项时,您应该扩展DefineStoreOptionsBase。与PiniaCustomProperties不同的是,它只公开两种泛型:StateStore类型,允许您限制可以定义的类型。例如,你可以使用actions的名称:

  1. import 'pinia'
  2. declare module 'pinia' {
  3. export interface DefineStoreOptionsBase<S, Store> {
  4. // allow defining a number of ms for any of the actions
  5. debounce?: Partial<Record<keyof StoreActions<Store>, number>>
  6. }
  7. }

TIP 还有一个StoreGetters类型用于从Store类型中提取getters。您还可以分别通过DefineStoreOptionsDefineSetupStoreOptions类型来扩展设置setup storesoption stores的选项。

Nuxt.js

NuxtPinia一起使用时,您必须先创建一个Nuxt插件。这将使您可以访问该Pinia实例:

  1. // plugins/myPiniaPlugin.js
  2. import { PiniaPluginContext } from 'pinia'
  3. import { Plugin } from '@nuxt/types'
  4. function MyPiniaPlugin({ store }: PiniaPluginContext) {
  5. store.$subscribe((mutation) => {
  6. // react to store changes
  7. console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}.`)
  8. })
  9. return { creationTime: new Date() }
  10. }
  11. const myPlugin: Plugin = ({ pinia }) {
  12. pinia.use(MyPiniaPlugin);
  13. }
  14. export default myPlugin

注意上面的例子使用的是TypeScript,如果你使用的是.js文件,你必须删除PiniaPluginContext的类型注释和Plugin的引入。