插件
只要通过 pinia.use(myPlugin) 注册过一次插件,之后每一个 store 被初始化(即第一次 useSomeStore())时,Pinia 都会把该插件函数重新执行一遍,并把当前的 store 的 PiniaPluginContext 传入。
因此:
- 插件会被重复调用多次(store 有多少个就多少次)。
- 如果只想对某些 store 生效,在插件里自己根据
context.store.$id或context.options做判断即可。
创建插件
Pinia 插件是一个函数,可以选择性地返回要添加到 store 的属性。它接收一个可选参数,即 context。
// 源码位置:pinia.d.ts
export interface Pinia {
use(plugin: PiniaPlugin): Pinia
}
export interface PiniaPlugin {
(context: PiniaPluginContext): void | Partial<Store> | PiniaCustomProperties
}export interface PiniaPluginContext<
Id extends string = string,
S extends StateTree = StateTree,
G = _GettersTree<S>,
A = _ActionsTree
> {
pinia: Pinia// 用 `createPinia()` 创建的 pinia。
app: App // 用 `createApp()` 创建的当前应用(仅 Vue 3)。
store: Store<Id, S, G, A>// 该插件想扩展的 store
options: DefineStoreOptions<Id, S, G, A>// 定义传给 `defineStore()` 的 store 的可选对象。
}注册插件
然后用 pinia.use() 将这个函数传给 pinia:
pinia.use(myPiniaPlugin)插件只会应用于在 pinia 传递给应用后创建的 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'这对添加全局对象很有用,如路由器、modal 或 toast 管理器。
扩展 Store
你可以直接通过在一个插件中返回包含特定属性的对象来为每个 store 都添加上特定属性:
pinia.use(() => ({ hello: 'world' }))你也可以直接在 store 上设置该属性,但可以的话,请使用返回对象的方法,这样它们就能被 devtools 自动追踪到:
pinia.use(({ store }) => {
store.hello = 'world'
})值得注意的是,每个 store 都被 reactive包装过,所以可以自动解包任何它所包含的 Ref(ref()、computed()...)。
在store上直接挂载一个响应式数据,他的使用和state和getter完全一致
const sharedRef = ref('shared')
pinia.use(({ store }) => {
// 每个 store 都有单独的 `hello` 属性
store.hello = ref('secret')
// 它会被自动解包
store.hello // 'secret'
// 所有的 store 都在共享 `shared` 属性的值
store.shared = sharedRef
store.shared // 'shared'
})添加新选项
在定义 store 时,可以创建新的选项,以便在插件中使用它们。例如,你可以创建一个 debounce 选项,允许你让任何 action 实现防抖。
defineStore('search', {
actions: {
searchContacts() {
// ...
},
},
// 这将在后面被一个插件读取
debounce: {
// 让 action searchContacts 防抖 300ms
searchContacts: 300,
},
})然后,该插件可以读取该选项来包装 action,并替换原始 action:
// 使用任意防抖库
import debounce from 'lodash/debounce'
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//同名会被覆盖
}, {})
}
})注意,在使用 setup 语法时,自定义选项作为第 3 个参数传递:
defineStore(
'search',
() => {
// ...
},
{
// 这将在后面被一个插件读取
debounce: {
// 让 action searchContacts 防抖 300ms
searchContacts: 300,
},
}
)为新的store属性添加TS
当在 store 中添加新的属性时,你也应该扩展 PiniaCustomProperties 接口。
import 'pinia'
declare module 'pinia' {
export interface PiniaCustomProperties {
// 通过使用一个 setter,我们可以允许字符串和引用。
set hello(value: string | Ref<string>)
get hello(): string
// 你也可以定义更简单的值
simpleNumber: number
// 添加路由(#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())
})