Plugins(插件) - Pinia(状态管理)
Plugins(插件)
虽然是轻量级 API,Pania 可以完全扩展。以下是您可以做的事情列表:
- 向 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'
这对于添加全局对象(如 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.use()
函数传递给 Pinia:
pinia.use(myPiniaPlugin)
插件仅适用于传递给应用后创建 Pinia 的 store,否则将不会被应用。
扩充 store
您可以通过简单地在插件中返回它们的对象来为每个商店添加属性:
pinia.use(() => ({ hello: 'world' }))
您也可以直接在 store 上设置属性,但如果可能,请使用返回版本,以便它们可以被 devtools 自动跟踪:
pinia.use(({ store }) => { store.hello = 'world' })
插件返回的任何属性都将由 devtools 自动跟踪,因此为了hello在 devtools 中可见,请确保将其添加到存储中。仅当您希望在 devtools 中调试时,才在 dev 模式下使用store._customProperties
:
// 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
的情况下,可以访问所有计算属性,以及它们是响应性的原因。
添加新 state
如果您想将新的 state 属性添加到store,或打算在实例化期间使用的属性,您必须在两个地方添加它:
- 在 store 中,您可以使用
store.myState
访问它。 - 在store.$state,以便在开发工具中使用,并在 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 (!Object.prototype.hasOwnProperty(store.$state, '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.org/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 相同的响应性警告。在创建新的 state 属性时,您需要使用Vue.set()
(Vue 2.7)或set()
(from @vue/composition-apifor Vue > 2.7)secret 和 hasError
import { set, toRef } from '@vue/composition-api' pinia.use(({ store }) => { if (!Object.prototype.hasOwnProperty(store.$state, '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', toRef(store.$state, 'secret')) store.secret // 'secret' })
添加新的外部属性
当添加外部属性、来自其他库的类实例或仅仅是非响应性的东西时,您应该在将对象markRaw()
传递给 Pinia 之前将其包装起来。下面一个将 router 添加到每个 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, }, })
然后插件可以读取该选项,以包装操作并替换原始操作:
// 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 }, {}) } })
请注意,使用设置语法时,自定义选项作为第三个参数传递:
defineStore( 'search', () => { // ... }, { // this will be read by a plugin later on debounce: { // debounce the action searchContacts by 300ms searchContacts: 300, }, } )
TypeScript
上面显示的所有内容都可以通过键入支持来完成,因此您无需使用any
或@ts-ignore
.
Typing 插件
Pinia 插件可以按如下方式键入:
import { PiniaPluginContext } from 'pinia' export function myPiniaPlugin(context: PiniaPluginContext) { // ... }
键入新的 state 属性
向 store 添加新属性时,您还应该扩展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) get hello(): string // you can define simpler values too simpleNumber: number } }
然后可以安全地写入和读取它:
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(这仅适用于选项存储):
pinia.use(({ options }) => ({ $options: options }))
我们可以通过使用 4 种通用类型来正确输入PiniaCustomProperties
:
import 'pinia' declare module 'pinia' { export interface PiniaCustomProperties { $options: { id: Id state?: () => S getters?: G actions?: A } } }
在泛型中扩展类型时,它们的命名必须与源代码中的完全相同。Id 不能命名为 id 或 I,S 不能命名为 State。以下是每个字母的含义:
- S: State
- G: Getters
- A: Actions
- SS: Setup Store / Store
输入新 state
当添加新的state 属性(store
和store.$state)时,您需要添加类型来PiniaCustomStateProperties
代替。与PiniaCustomProperties
不同的是,它只接收State泛型:
import 'pinia' declare module 'pinia' { export interface PiniaCustomStateProperties { hello: string } }
键入新的创建选项
在为defineStore()
创建新选项时,您应该扩展DefineStoreOptionsBase
。与PiniaCustomProperties
不同的是,它只公开了两个泛型:State和Store类型,允许您限制可以定义的内容。例如,您可以使用操作的名称:
import 'pinia' declare module 'pinia' { export interface DefineStoreOptionsBase { // allow defining a number of ms for any of the actions debounce?: Partial } }
还有一个StoreGetters类型用于从存储类型中提取 getter。您还可以仅通过分别扩展类型DefineStoreOptions和definestetupstoreoptions来扩展设置存储或选项存储的选项。
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) => { // 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
请注意,上面的示例使用的是 TypeScript,如果您使用的是文件,则必须删除类型注释PiniaPluginContext及其导入Plugin.js
鹏仔微信 15129739599 鹏仔QQ344225443 鹏仔前端 pjxi.com 共享博客 sharedbk.com
图片声明:本站部分配图来自网络。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!