• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • 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.$subscribestore.$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<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


    输入新 state

    当添加新的state 属性(storestore.$state)时,您需要添加类型来PiniaCustomStateProperties代替。与PiniaCustomProperties不同的是,它只接收State泛型:

    import 'pinia'
    
    declare module 'pinia' {
      export interface PiniaCustomStateProperties {
        hello: string
      }
    }
    


    键入新的创建选项

    在为defineStore()创建新选项时,您应该扩展DefineStoreOptionsBase。与PiniaCustomProperties不同的是,它只公开了两个泛型:StateStore类型,允许您限制可以定义的内容。例如,您可以使用操作的名称:

    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类型用于从存储类型中提取 getter。您还可以仅通过分别扩展类型DefineStoreOptionsdefinestetupstoreoptions来扩展设置存储或选项存储的选项。


    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