Skip to content
目录导航

Plugins

由于低级 API,Pania 商店可以完全扩展。 以下是您可以执行的操作列表:

  • 向商店添加新属性
  • 定义商店时添加新选项
  • 为商店添加新方法
  • 包装现有方法
  • 更改甚至取消操作
  • 实现类似 Local Storage 的副作用
  • 适用于特定商店

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

js
import { createPinia } from 'pinia'

// 为安装此插件后创建的每个商店添加一个名为 `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'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这对于添加全局对象(如路由器、模式或 toast 管理器)很有用。

介绍

Pinia 插件是一个函数,可以选择返回要添加到商店的属性。 它需要一个可选参数,一个 context

js
export function myPiniaPlugin(context) {
  context.pinia // 使用 `createPinia()` 创建的 pinia
  context.app // 使用 `createApp()` 创建的当前应用程序(仅限 Vue 3)
  context.store // 插件正在扩充的商店
  context.options // 定义存储的选项对象传递给`defineStore()`
  // ...
}
1
2
3
4
5
6
7

然后使用 pinia.use() 将此函数传递给 pinia

js
pinia.use(myPiniaPlugin)
1

插件仅适用于在将pinia传递给应用程序后创建的商店,否则将不会被应用。

扩充商店

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

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

您也可以直接在 store 上设置属性,但如果可能,请使用返回版本,以便 devtools 可以自动跟踪它们

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

插件的任何属性_returned_都会被devtools自动跟踪,所以为了让hello在devtools中可见,如果你想调试它,请确保将它添加到store._customProperties仅在开发模式 开发工具:

js
// 从上面的例子
pinia.use(({ store }) => {
  store.hello = 'world'
  // 确保您的捆绑器处理此问题。 webpack 和 vite 应该默认这样做
  if (process.env.NODE_ENV === 'development') {
    // 添加您在商店中设置的任何密钥
    store._customProperties.add('hello')
  }
})
1
2
3
4
5
6
7
8
9

请注意,每个 store 都使用 reactive 包装,自动展开任何 Ref (ref(), computed() , ...) 它包含:

js
const sharedRef = ref('shared')
pinia.use(({ store }) => {
  // 每个商店都有自己的 `hello` 属性
  store.hello = ref('secret')
  // 它会自动展开
  store.hello // 'secret'

  // 所有商店都共享 value `shared` 属性
  store.shared = sharedRef
  store.shared // 'shared'
})
1
2
3
4
5
6
7
8
9
10
11
js
import { toRef, ref } from 'vue'

pinia.use(({ store }) => {
  // 为了正确处理 SSR,我们需要确保我们没有覆盖现有值
  if (!Object.prototype.hasOwnProperty(store.$state, 'hasError')) {
    // hasError 在插件中定义,因此每个商店都有自己的状态属性
    const hasError = ref(false)
    // 在 $state 上设置变量,允许在 SSR 期间对其进行序列化
    store.$state.hasError = hasError
  }
  // 我们需要将 ref 从 state 传输到 store,这样
  // 两个访问:store.hasError 和 store.$state.hasError 都可以工作并共享同一个变量
  // See https://vuejs.org/api/reactivity-utilities.html#toref
  store.hasError = toRef(store.$state, 'hasError')

  // 在这种情况下,最好不要返回 `hasError`,因为无论如何它都会
  // 显示在 devtools 的 `state` 部分中,如果我们返回它,devtools 会显示两次。
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

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

WARNING

如果您使用 Vue 2,Pinia 会受到与 Vue 一样的相同的响应式警告。 在创建像 secrethasError 这样的新状态属性时,您需要使用 Vue.set() (Vue 2.7) 或set()(from @vue/composition-api for Vue <2.7):

js
import { set, toRef } from '@vue/composition-api'
pinia.use(({ store }) => {
  if (!Object.prototype.hasOwnProperty(store.$state, 'hello')) {
    const secretRef = ref('secret')
    // 如果数据打算在 SSR 期间使用,您应该在 `$state` 
    // 属性上设置它,以便在水合期间对其进行序列化和拾取
    set(store.$state, 'secret', secretRef)
  }
  // 也可以直接在商店中设置它,这样您就可以通过两种方式
  // 访问它:`store.$state.secret` / `store.secret`
  set(store, 'secret', toRef(store.$state, 'secret'))
  store.secret // 'secret'
})
1
2
3
4
5
6
7
8
9
10
11
12
13

添加新的外部属性

当添加外部属性、来自其他库的类实例或仅仅是非反应性的东西时,您应该在将对象传递给 pinia 之前使用 markRaw() 包装对象。 这是一个将路由器添加到每个商店的示例:

js
import { markRaw } from 'vue'
// 根据您的路由器所在的位置进行调整
import { router } from './router'

pinia.use(({ store }) => {
  store.router = markRaw(router)
})
1
2
3
4
5
6
7

在插件中调用 $subscribe

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

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

添加新选项

可以在定义商店时创建新选项,以便以后从插件中使用它们。 例如,您可以创建一个 debounce 选项,允许您对任何操作进行去抖动:

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

  // 稍后将由插件读取
  debounce: {
    // 将动作 searchContacts 去抖动 300ms
    searchContacts: 300,
  },
})
1
2
3
4
5
6
7
8
9
10
11
12
13

然后插件可以读取该选项以包装操作并替换原始操作:

js
// 使用任何去抖库
import debounce from 'lodash/debounce'

pinia.use(({ options, store }) => {
  if (options.debounce) {
    // 我们正在用新的行动覆盖这些行动
    return Object.keys(options.debounce).reduce((debouncedActions, action) => {
      debouncedActions[action] = debounce(
        store[action],
        options.debounce[action]
      )
      return debouncedActions
    }, {})
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

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

js
defineStore(
  'search',
  () => {
    // ...
  },
  {
    // 稍后将由插件读取
    debounce: {
      // 将动作 searchContacts 去抖动 300ms
      searchContacts: 300,
    },
  }
)
1
2
3
4
5
6
7
8
9
10
11
12
13

TypeScript

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

Typing plugins

Pinia 插件可以按如下方式键入:

ts
import { PiniaPluginContext } from 'pinia'

export function myPiniaPlugin(context: PiniaPluginContext) {
  // ...
}
1
2
3
4
5

键入新的商店属性

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

ts
import 'pinia'

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

    // 你也可以定义更简单的值
    simpleNumber: number
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

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

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

  store.simpleNumber = Math.random()
  // @ts-expect-error: 我们没有正确输入
  store.simpleNumber = ref(Math.random())
})
1
2
3
4
5
6
7
8

PiniaCustomProperties 是一种通用类型,允许您引用商店的属性。 想象以下示例,我们将初始选项复制为$options(这仅适用于选项式API):

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

我们可以使用 4 种通用类型的 PiniaCustomProperties 来正确输入:

ts
import 'pinia'

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

TIP

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

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

输入新状态

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

ts
import 'pinia'

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

键入新的创建选项

在为 defineStore() 创建新选项时,您应该扩展 DefineStoreOptionsBase。 与 PiniaCustomProperties 不同,它只公开了两个泛型:State 和 Store 类型,允许您限制可以定义的内容。 例如,您可以使用操作的名称:

ts
import 'pinia'

declare module 'pinia' {
  export interface DefineStoreOptionsBase<S, Store> {
    // 允许为任何操作定义毫秒数
    debounce?: Partial<Record<keyof StoreActions<Store>, number>>
  }
}
1
2
3
4
5
6
7
8

TIP

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

Nuxt.js

在 Nuxt 旁边使用 pinia 时,您必须先创建一个 Nuxt 插件 . 这将使您可以访问 pinia 实例:

ts
// plugins/myPiniaPlugin.ts
import { PiniaPluginContext } from 'pinia'

function MyPiniaPlugin({ store }: PiniaPluginContext) {
  store.$subscribe((mutation) => {
    // react to store changes
    console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}.`)
  })

  // Note this has to be typed if you are using TS
  return { creationTime: new Date() }
}

export default defineNuxtPlugin(({ $pinia }) => {
  $pinia.use(MyPiniaPlugin)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

请注意,上面的示例使用的是 TypeScript,如果您使用的是 .js 文件,则必须删除类型注释 PiniaPluginContextPlugin 以及它们的导入。

Nuxt.js 2

如果您使用的是 Nuxt.js 2,则类型略有不同:

ts
// plugins/myPiniaPlugin.ts
import { PiniaPluginContext } from 'pinia'
import { Plugin } from '@nuxt/types'

function MyPiniaPlugin({ store }: PiniaPluginContext) {
  store.$subscribe((mutation) => {
    // react to store changes
    console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}.`)
  })

  // 请注意,如果您使用的是 TS,则必须输入
  return { creationTime: new Date() }
}

const myPlugin: Plugin = ({ $pinia }) => {
  $pinia.use(MyPiniaPlugin)
}

export default myPlugin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19