插件
Pinia Store 可以通过低级 API 完全扩展。以下列出了您可以执行的操作
- 向 Store 添加新属性
- 在定义 Store 时添加新选项
- 向 Store 添加新方法
- 包装现有方法
- 拦截 Action 及其结果
- 实现副作用,例如 本地存储
- 仅应用于特定 Store
插件使用 pinia.use()
添加到 pinia 实例中。最简单的示例是通过返回一个对象来向所有 Store 添加一个静态属性
import { createPinia } from 'pinia'
// add a property named `secret` to every store that is created
// after this plugin is installed this could be in a different file
function SecretPiniaPlugin() {
return { secret: 'the cake is a lie' }
}
const pinia = createPinia()
// give the plugin to pinia
pinia.use(SecretPiniaPlugin)
// in another file
const store = useStore()
store.secret // 'the cake is a lie'
这对于添加全局对象(如路由器、模态框或吐司管理器)很有用。
介绍
Pinia 插件是一个函数,它可以选择性地返回要添加到 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()
传递给 pinia
pinia.use(myPiniaPlugin)
插件仅应用于在插件本身之后以及将 pinia
传递给应用程序之后创建的 Store,否则它们将不会被应用。
增强 Store
您可以通过在插件中简单地返回一个包含这些属性的对象来向每个 Store 添加属性
pinia.use(() => ({ hello: 'world' }))
您也可以直接在 store
上设置属性,但如果可能,请使用返回版本,以便它们可以由 devtools 自动跟踪
pinia.use(({ store }) => {
store.hello = 'world'
})
插件返回的任何属性都将由 devtools 自动跟踪,因此为了使 hello
在 devtools 中可见,请确保在仅限开发模式下将其添加到 store._customProperties
中,如果您想在 devtools 中调试它
// 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') {
// add any keys you set on the store
store._customProperties.add('hello')
}
})
请注意,每个 Store 都使用 reactive
包装,自动解包它包含的任何 Ref(ref()
、computed()
、...)
const sharedRef = ref('shared')
pinia.use(({ store }) => {
// each store has its individual `hello` property
store.hello = ref('secret')
// it gets automatically unwrapped
store.hello // 'secret'
// all stores are sharing the value `shared` property
store.shared = sharedRef
store.shared // 'shared'
})
这就是为什么您可以访问所有计算属性而无需 .value
以及它们为何是响应式的。
添加新状态
如果您想向 Store 添加新的状态属性或在水合过程中要使用的属性,您需要在两个地方添加它
- 在
store
上,以便您可以使用store.myState
访问它 - 在
store.$state
上,以便它可以在 devtools 中使用,并且在 SSR 期间被序列化。
最重要的是,您肯定需要使用 ref()
(或其他响应式 API)才能在不同的访问中共享值
import { toRef, ref } from 'vue'
pinia.use(({ store }) => {
// to correctly handle SSR, we need to make sure we are not overriding an
// existing value
if (!store.$state.hasOwnProperty('hasError')) {
// hasError is defined within the plugin, so each store has their individual
// state property
const hasError = ref(false)
// setting the variable on `$state`, allows it be serialized during SSR
store.$state.hasError = hasError
}
// we need to transfer the ref from the state to the store, this way
// both accesses: store.hasError and store.$state.hasError will work
// and share the same variable
// See https://vuejs.ac.cn/api/reactivity-utilities.html#toref
store.hasError = toRef(store.$state, 'hasError')
// in this case it's better not to return `hasError` since it
// will be displayed in the `state` section in the devtools
// anyway and if we return it, devtools will display it twice.
})
请注意,在插件中发生的任何状态更改或添加(包括调用 store.$patch()
)都发生在 Store 处于活动状态之前,因此不会触发任何订阅。
警告
如果您使用的是Vue 2,Pinia 会受到与 Vue 相同的响应式性注意事项。您需要使用 Vue.set()
(Vue 2.7)或 set()
(来自 @vue/composition-api
用于 Vue <2.7)来创建新的状态属性,例如 secret
和 hasError
import { set, toRef } from '@vue/composition-api'
pinia.use(({ store }) => {
if (!store.$state.hasOwnProperty('secret')) {
const secretRef = ref('secret')
// If the data is meant to be used during SSR, you should
// set it on the `$state` property so it is serialized and
// picked up during hydration
set(store.$state, 'secret', secretRef)
}
// set it directly on the store too so you can access it
// both ways: `store.$state.secret` / `store.secret`
set(store, 'secret', toRef(store.$state, 'secret'))
store.secret // 'secret'
})
重置插件中添加的状态
默认情况下,$reset()
不会重置插件添加的状态,但您可以覆盖它以重置您添加的状态
import { toRef, ref } from 'vue'
pinia.use(({ store }) => {
// this is the same code as above for reference
if (!store.$state.hasOwnProperty('hasError')) {
const hasError = ref(false)
store.$state.hasError = hasError
}
store.hasError = toRef(store.$state, 'hasError')
// make sure to set the context (`this`) to the store
const originalReset = store.$reset.bind(store)
// override the $reset function
return {
$reset() {
originalReset()
store.hasError = false
},
}
})
添加新的外部属性
在添加外部属性、来自其他库的类实例或只是非响应式内容时,您应该在将对象传递给 pinia 之前使用 markRaw()
包装该对象。以下是在每个 Store 中添加路由器的示例
import { markRaw } from 'vue'
// adapt this based on where your router is
import { router } from './router'
pinia.use(({ store }) => {
store.router = markRaw(router)
})
在插件中调用 $subscribe
您也可以在插件中使用 store.$subscribe 和 store.$onAction
pinia.use(({ store }) => {
store.$subscribe(() => {
// react to store changes
})
store.$onAction(() => {
// react to store actions
})
})
添加新选项
在定义 Store 时创建新选项以供插件稍后使用是可能的。例如,您可以创建一个 debounce
选项,它允许您对任何 Action 进行防抖
defineStore('search', {
actions: {
searchContacts() {
// ...
},
},
// this will be read by a plugin later on
debounce: {
// debounce the action searchContacts by 300ms
searchContacts: 300,
},
})
然后,插件可以读取该选项以包装 Action 并替换原始 Action
// use any debounce library
import debounce from 'lodash/debounce'
pinia.use(({ options, store }) => {
if (options.debounce) {
// we are overriding the actions with new ones
return Object.keys(options.debounce).reduce((debouncedActions, action) => {
debouncedActions[action] = debounce(
store[action],
options.debounce[action]
)
return debouncedActions
}, {})
}
})
请注意,自定义选项在使用 setup 语法时作为第三个参数传递
defineStore(
'search',
() => {
// ...
},
{
// this will be read by a plugin later on
debounce: {
// debounce the action searchContacts by 300ms
searchContacts: 300,
},
}
)
TypeScript
上面显示的所有内容都可以使用类型支持来完成,因此您永远不需要使用 any
或 @ts-ignore
。
键入插件
Pinia 插件可以按如下方式键入
import { PiniaPluginContext } from 'pinia'
export function myPiniaPlugin(context: PiniaPluginContext) {
// ...
}
键入新的 Store 属性
在向 Store 添加新属性时,您还应该扩展 PiniaCustomProperties
接口。
import 'pinia'
import type { Router } from 'vue-router'
declare module 'pinia' {
export interface PiniaCustomProperties {
// by using a setter we can allow both strings and refs
set hello(value: string | Ref<string>)
get hello(): string
// you can define simpler values too
simpleNumber: number
// type the router added by the plugin above (#adding-new-external-properties)
router: Router
}
}
然后可以安全地编写和读取它
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())
})
PiniaCustomProperties
是一个泛型类型,它允许您引用 Store 的属性。想象一下以下示例,我们将在其中将初始选项复制为 $options
(这仅适用于选项 Store)
pinia.use(({ options }) => ({ $options: options }))
我们可以通过使用 PiniaCustomProperties
的 4 个泛型类型来正确地键入它
import 'pinia'
declare module 'pinia' {
export interface PiniaCustomProperties<Id, S, G, A> {
$options: {
id: Id
state?: () => S
getters?: G
actions?: A
}
}
}
提示
在泛型中扩展类型时,它们必须与源代码中的名称完全相同。Id
不能命名为 id
或 I
,S
不能命名为 State
。以下是每个字母代表的内容
- S: 状态
- G: Getter
- A: Action
- SS: 设置 Store / Store
键入新的状态
在添加新的状态属性(到 store
和 store.$state
)时,您需要将类型添加到 PiniaCustomStateProperties
中。与 PiniaCustomProperties
不同,它只接收 State
泛型
import 'pinia'
declare module 'pinia' {
export interface PiniaCustomStateProperties<S> {
hello: string
}
}
键入新的创建选项
在为 defineStore()
创建新选项时,您应该扩展 DefineStoreOptionsBase
。与 PiniaCustomProperties
不同,它只公开两个泛型:State 和 Store 类型,允许您限制可以定义的内容。例如,您可以使用 Action 的名称
import 'pinia'
declare module 'pinia' {
export interface DefineStoreOptionsBase<S, Store> {
// allow defining a number of ms for any of the actions
debounce?: Partial<Record<keyof StoreActions<Store>, number>>
}
}
提示
还有一个 StoreGetters
类型用于从 Store 类型中提取Getter。您还可以通过分别扩展类型 DefineStoreOptions
和 DefineSetupStoreOptions
来扩展设置 Store 或选项 Store 的选项。
Nuxt.js
当将 pinia 与 Nuxt 一起使用时,您需要先创建一个Nuxt 插件。这将使您可以访问 pinia
实例
// 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)
})
信息
上面的示例使用的是 TypeScript,如果您使用的是 .js
文件,则必须删除类型注释 PiniaPluginContext
和 Plugin
以及它们的导入。
Nuxt.js 2
如果您使用的是 Nuxt.js 2,则类型略有不同
// 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}.`)
})
// Note this has to be typed if you are using TS
return { creationTime: new Date() }
}
const myPlugin: Plugin = ({ $pinia }) => {
$pinia.use(MyPiniaPlugin)
}
export default myPlugin