# Plugins
由于低版本的API
,Pinia
的stores
可以完全扩展。下面是一些你可以做的事情:
- 向
stores
添加新的属性 - 在定义
stores
时添加新选项 - 向
stores
添加新方法 - 包装现有的方法
- 更改甚至取消操作
- 实现像本地存储这样的功能
- 只适用于特定的
stores
使用pinia.use()
将插件添加到pinia
实例中。最简单的例子是通过返回一个对象向所有stores
添加一个静态属性:
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'
这对于添加全局对象(如router
、modal
或toast
管理器)非常有用。
# 介绍
Pinia
的插件是一个函数,可以选择返回要添加到store
中的属性。它有一个可选参数 context
:
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
的pinia.use()
:
pinia.use(myPiniaPlugin)
插件只应用于stores
被创建在pinia
传递给应用程序后 ,否则它们不会被应用。
# 扩展 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
(opens new window)包装,并且会自动解包它包含的任何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
或computed
属性:
const globalSecret = ref('secret')
pinia.use(({ store }) => {
// `secret` is shared among all stores
store.$state.secret = globalSecret
store.secret = globalSecret
// it gets automatically unwrapped
store.secret // 'secret'
const hasError = ref(false)
store.$state.hasError = hasError
// this one must always be set
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
激活之前,因此不会触发任何订阅。
WARNING 如果您使用的是
Vue 2
,Pinia
将受到与Vue
相同的反应警告。当创建新的状态属性如secret
和hasError
时,您需要使用来自@vue/composition-api
的set
方法。
import { set } from '@vue/composition-api'
pinia.use(({ store }) => {
if (!store.$state.hasOwnProperty('hello')) {
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', secretRef)
store.secret // 'secret'
}
})
# 添加新的外部属性
当添加外部属性,来自其他库的类实例或简单的非响应式对象时,应该在将对象传递给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 (opens new window)和store.$onAction (opens new window):
pinia.use(({ store }) => {
store.$subscribe(() => {
// react to store changes
})
store.$onAction(() => {
// react to store actions
})
})
# 添加新选项
可以在定义stores
时创建新的选项,以便随后从插件中使用它们。例如,你可以创建一个debounce
选项,允许你对任何操作进行debounce
:
defineStore('search', {
actions: {
searchContacts() {
// ...
},
},
// this will be read by a plugin later on
debounce: {
// debounce the action searchContacts by 300ms
searchContacts: 300,
},
})
插件可以读取该选项来包装actions
并替换原来的actions
:
// use any debounce library
import debounce from 'lodash/debunce'
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属性
当向stores
添加新属性时,您还应该扩展PiniaCustomProperties
接口。
import 'pinia'
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
}
}
然后可以安全地写入和读取:
pinia.use(({ store }) => {
store.hello = 'Hola'
store.hello = ref('Hola')
store.number = Math.random()
// @ts-expect-error: we haven't typed this correctly
store.number = ref(Math.random())
})
PiniaCustomProperties
是一个泛型类型,允许您引用store
的属性。想象一下下面的示例,我们将初始选项复制为$options
(这仅适用于option stores
):
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
}
}
}
TIP
在泛型中扩展类型时,它们的命名必须与源码中的完全相同。
Id
不能命名为id
或I
,S
也不能命名为State
。以下是每个字母所代表的含义:
- S: State
- G: Getters
- A: Actions
- SS: Setup Store / Store
# 编写新的状态
当添加新的状态属性时(同时添加到store
和store.$state
),您需要将类型添加到PiniaCustomStateProperties
。与PiniaCustomProperties
不同的是,它只接收State
泛型:
import 'pinia'
declare module 'pinia' {
export interface PiniaCustomStateProperties<S> {
hello: string
}
}
# 编写新的创建选项
当为defineStore()
创建新选项时,您应该扩展DefineStoreOptionsBase
。与PiniaCustomProperties
不同的是,它只公开两种泛型:State
和Store
类型,允许您限制可以定义的类型。例如,你可以使用actions
的名称:
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>>
}
}
TIP 还有一个
StoreGetters
类型用于从Store
类型中提取getters
。您还可以分别通过DefineStoreOptions
和DefineSetupStoreOptions
类型来扩展设置setup stores
或option stores
的选项。
# Nuxt.js
当Nuxt
和Pinia
一起使用时,您必须先创建一个Nuxt
插件 (opens new window)。这将使您可以访问该Pinia
实例:
// plugins/myPiniaPlugin.js
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}.`)
})
return { creationTime: new Date() }
}
const myPlugin: Plugin = ({ pinia }) {
pinia.use(MyPiniaPlugin);
}
export default myPlugin
注意上面的例子使用的是TypeScript
,如果你使用的是.js
文件,你必须删除PiniaPluginContext
的类型注释和Plugin
的引入。
← Actions 组件外使用Store →