插件
由于是底层 API,Pania Store可以完全扩展。 以下是您可以执行的操作列表:
- 向 Store 添加新属性
- 定义 Store 时添加新选项
- 为 Store 添加新方法
- 包装现有方法
- 更改甚至取消操作
- 实现本地存储等副作用
- 仅适用于特定 Store
使用 pinia.use()
将插件添加到 pinia 实例中。 最简单的例子是通过返回一个对象为所有Store添加一个静态属性:
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'
这对于添加全局对象(如路由器、模式或 toast 管理器)很有用。
介绍
Pinia 插件是一个函数,可以选择返回要添加到 store 的属性。 它需要一个可选参数,一个 context:
export function myPiniaPlugin(context) {
context.pinia // 使用 `createPinia()` 创建的 pinia
context.app // 使用 `createApp()` 创建的当前应用程序(仅限 Vue 3)
context.store // 插件正在扩充的 store
context.options // 定义存储的选项对象传递给`defineStore()`
// ...
}
然后使用 pinia.use()
将此函数传递给 pinia
:
pinia.use(myPiniaPlugin)
插件仅适用于**在将pinia
传递给应用程序后创建的 store **,否则将不会被应用。
扩充 store
您可以通过简单地在插件中返回它们的对象来为每个 store 添加属性:
pinia.use(() => ({ hello: 'world' }))
您也可以直接在 store
上设置属性,但如果可能,请使用返回版本,以便 devtools 可以自动跟踪它们:
pinia.use(({ store }) => {
store.hello = 'world'
})
插件的任何属性 returned 都会被devtools自动跟踪,所以为了让hello
在devtools中可见,如果你想调试它,请确保将它添加到store._customProperties
仅在开发模式 开发工具:
// 从上面的例子
pinia.use(({ store }) => {
store.hello = 'world'
// 确保您的打包器可以处理这个问题。 webpack 和 vite 应该默认这样做
if (process.env.NODE_ENV === 'development') {
// 添加您在 store 中设置的任何 keys
store._customProperties.add('hello')
}
})
请注意,每个 store 都使用 reactive
包装,自动展开任何 Ref (ref()
, computed()
, ...) 它包含了:
const sharedRef = ref('shared')
pinia.use(({ store }) => {
// 每个 store 都有自己的 `hello` 属性
store.hello = ref('secret')
// 它会自动展开
store.hello // 'secret'
// 所有 store 都共享 value `shared` 属性
store.shared = sharedRef
store.shared // 'shared'
})
这就是为什么您可以在没有 .value
的情况下访问所有计算属性以及它们是响应式的原因。
添加新状态
如果您想将新的状态属性添加到 store 或打算在 hydration 中使用的属性,您必须在两个地方添加它:
- 在
store
上,因此您可以使用store.myState
访问它 - 在
store.$state
上,因此它可以在 devtools 中使用,并且在 SSR 期间被序列化。
请注意,这允许您共享 ref
或 computed
属性:
const globalSecret = ref('secret')
pinia.use(({ store }) => {
// `secret` 在所有 store 之间共享
store.$state.secret = globalSecret
store.secret = globalSecret
// 它会自动展开
store.secret // 'secret'
const hasError = ref(false)
store.$state.hasError = hasError
// 这个必须始终设置
store.hasError = toRef(store.$state, 'hasError')
// 在这种情况下,最好不要返回 `hasError`,因为它
// 将显示在 devtools 的 `state` 部分
// 无论如何,如果我们返回它,devtools 将显示它两次。
})
请注意,插件中发生的状态更改或添加(包括调用store.$patch()
)发生在存储处于活动状态之前,因此不会触发任何订阅。
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')
// 如果数据打算在 SSR 期间使用,您应该
// 将它设置在 `$state` 属性上,以便它被序列化并
// 在 hydration 中被提取
set(store.$state, 'secret', secretRef)
// 也可以直接在 store 中设置它,以便您可以访问它
// 两种方式:`store.$state.secret` / `store.secret`
set(store, 'secret', secretRef)
store.secret // 'secret'
}
})
添加新的外部属性
当添加外部属性、来自其他库的类实例或仅仅是非响应式的东西时,您应该在将对象传递给 pinia 之前使用 markRaw()
包装对象。 这是一个将路由添加到每个 store 的示例:
import { markRaw } from 'vue'
// 根据您的路由所在的位置进行调整
import { router } from './router'
pinia.use(({ store }) => {
store.router = markRaw(router)
})
在插件中调用 $subscribe
您也可以在插件中使用 store.$subscribe 和 store.$onAction :
pinia.use(({ store }) => {
store.$subscribe(() => {
// 在存储变化的时候执行
})
store.$onAction(() => {
// 在 action 的时候执行
})
})
添加新选项
可以在定义 store 时创建新选项,以便以后从插件中使用它们。 例如,您可以创建一个 debounce
选项,允许您对任何操作进行去抖动:
defineStore('search', {
actions: {
searchContacts() {
// ...
},
},
// 稍后将由插件读取
debounce: {
// 将动作 searchContacts 防抖 300ms
searchContacts: 300,
},
})
然后插件可以读取该选项以包装操作并替换原始操作:
// 使用任何防抖库
import debounce from 'lodash/debunce'
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
}, {})
}
})
请注意,使用设置语法时,自定义选项作为第三个参数传递:
defineStore(
'search',
() => {
// ...
},
{
// 稍后将由插件读取
debounce: {
// 将动作 searchContacts 防抖 300ms
searchContacts: 300,
},
}
)
TypeScript
上面显示的所有内容都可以通过键入支持来完成,因此您无需使用 any
或 @ts-ignore
。
Typing 插件
Pinia 插件可以按如下方式引入:
import { PiniaPluginContext } from 'pinia'
export function myPiniaPlugin(context: PiniaPluginContext) {
// ...
}
引入新的 store 属性
向 store 添加新属性时,您还应该扩展 PiniaCustomProperties
接口。
import 'pinia'
declare module 'pinia' {
export interface PiniaCustomProperties {
// 通过使用 setter,我们可以同时允许字符串和引用
set hello(value: string | Ref<string>)
get hello(): string
// 你也可以定义更简单的值
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”(这仅适用于选项存储):
pinia.use(({ options }) => ({ $options: options }))
我们可以使用 4 种通用类型的 PiniaCustomProperties
来正确输入:
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: 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 类型,允许您限制可以定义的内容。 例如,您可以使用操作的名称:
import 'pinia'
declare module 'pinia' {
export interface DefineStoreOptionsBase<S, Store> {
// 允许为任何操作定义毫秒数
debounce?: Partial<Record<keyof StoreActions<Store>, number>>
}
}
TIP
还有一个 StoreGetters
类型可以从 Store 类型中提取 getters。 您还可以通过分别扩展 DefineStoreOptions
和 DefineSetupStoreOptions
类型来扩展 setup stores 或 option stores only 的选项。
Nuxt.js
当在 Nuxt 使用 pinia 时,您必须先创建一个 Nuxt 插件 . 这将使您可以访问 pinia
实例:
// plugins/myPiniaPlugin.js
import { PiniaPluginContext } from 'pinia'
import { Plugin } from '@nuxt/types'
function MyPiniaPlugin({ store }: PiniaPluginContext) {
store.$subscribe((mutation) => {
// 在存储变化的时候执行
console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}.`)
})
return { creationTime: new Date() }
}
const myPlugin: Plugin = ({ pinia }) {
pinia.use(MyPiniaPlugin);
}
export default myPlugin
请注意,上面的示例使用的是 TypeScript,如果您使用的是 .js 文件,则必须删除类型注释 PiniaPluginContext
和 Plugin
以及它们的导入。