阿里云主机折上折
  • 微信号
Current Site:Index > Development of Pinia plugins

Development of Pinia plugins

Author:Chuan Chen 阅读数:24887人阅读 分类: Vue.js

Basic Concepts of Pinia Plugins

Pinia plugins are powerful tools for extending Pinia's functionality, allowing developers to inject custom logic into the lifecycle of a store. Essentially, a plugin is a function that receives a context object as a parameter, providing access to the store instance and the app instance.

import { createPinia } from 'pinia'

const pinia = createPinia()

function myPlugin({ store, app }) {
  // Plugin logic
}

pinia.use(myPlugin)

Core APIs for Plugin Development

Pinia plugins primarily rely on the following core APIs:

  1. store.$onAction - Listen for action calls
  2. store.$subscribe - Listen for state changes
  3. store.$patch - Modify state
  4. store.$state - Access the entire state
function loggerPlugin({ store }) {
  store.$onAction(({ name, store, args, after, onError }) => {
    console.log(`Action "${name}" called with args:`, args)
    after((result) => {
      console.log(`Action "${name}" completed with result:`, result)
    })
    onError((error) => {
      console.error(`Action "${name}" failed with error:`, error)
    })
  })
}

Developing a State Persistence Plugin

A common use case for plugins is implementing state persistence. Below is a complete implementation of a localStorage persistence plugin:

function persistPlugin({ store }) {
  // Load initial state from localStorage
  const savedState = localStorage.getItem(`pinia-${store.$id}`)
  if (savedState) {
    store.$patch(JSON.parse(savedState))
  }

  // Listen for state changes and save them
  store.$subscribe((mutation, state) => {
    localStorage.setItem(`pinia-${store.$id}`, JSON.stringify(state))
  })
}

// Using the plugin
const pinia = createPinia()
pinia.use(persistPlugin)

Developing a Multi-Tab Synchronization Plugin

Another practical scenario is synchronizing Pinia state across multiple browser tabs:

function syncTabsPlugin({ store }) {
  // Listen for storage events
  window.addEventListener('storage', (event) => {
    if (event.key === `pinia-${store.$id}`) {
      store.$patch(JSON.parse(event.newValue))
    }
  })

  // Save state changes
  store.$subscribe((mutation, state) => {
    localStorage.setItem(`pinia-${store.$id}`, JSON.stringify(state))
  })
}

Developing a Request Caching Plugin

For stores that frequently fetch data, a caching plugin can be developed:

function cachePlugin({ store }) {
  const cache = new Map()

  // Wrap original actions
  const originalActions = { ...store.$actions }
  
  Object.keys(originalActions).forEach((actionName) => {
    store[actionName] = async function(...args) {
      const cacheKey = `${actionName}-${JSON.stringify(args)}`
      
      if (cache.has(cacheKey)) {
        return cache.get(cacheKey)
      }
      
      const result = await originalActions[actionName].call(store, ...args)
      cache.set(cacheKey, result)
      return result
    }
  })
}

Dependency Injection in Plugins

Pinia plugins can leverage Vue's provide/inject mechanism to share functionality:

function apiPlugin({ app }) {
  const api = axios.create({
    baseURL: 'https://api.example.com'
  })
  
  app.provide('api', api)
}

// Using it in a component
export default {
  inject: ['api'],
  methods: {
    async fetchData() {
      const response = await this.api.get('/data')
      // ...
    }
  }
}

Developing TypeScript-Supported Plugins

Adding TypeScript support to plugins can enhance the development experience:

import { PiniaPluginContext } from 'pinia'

interface PluginOptions {
  prefix?: string
}

declare module 'pinia' {
  export interface PiniaCustomProperties {
    $logger: (message: string) => void
  }
}

function loggerPlugin(options: PluginOptions = {}) {
  return ({ store }: PiniaPluginContext) => {
    store.$logger = (message: string) => {
      console.log(`[${options.prefix || 'LOG'}] ${store.$id}: ${message}`)
    }
  }
}

Plugin Composition and Execution Order

Multiple plugins can be combined, and they execute in the order they are registered:

const pinia = createPinia()

// loggerPlugin executes first, followed by persistPlugin
pinia.use(loggerPlugin)
pinia.use(persistPlugin)

// Can also be chained
pinia
  .use(loggerPlugin)
  .use(persistPlugin)

Developing a Devtools Integration Plugin

Add development tool support for Pinia stores:

function devtoolsPlugin({ store, app }) {
  if (process.env.NODE_ENV === 'development') {
    store._devtools = {
      id: store.$id,
      state: store.$state,
      actions: Object.keys(store.$actions)
    }
    
    // Send to Vue Devtools
    app.config.globalProperties.$devtools?.send('pinia:store-init', store._devtools)
  }
}

Error Handling in Plugins

Robust plugins should include error handling mechanisms:

function safePlugin({ store }) {
  const originalActions = { ...store.$actions }
  
  Object.keys(originalActions).forEach((actionName) => {
    store[actionName] = async function(...args) {
      try {
        return await originalActions[actionName].call(store, ...args)
      } catch (error) {
        console.error(`Error in action ${actionName}:`, error)
        throw error // Can choose to rethrow or handle the error
      }
    }
  })
}

Plugin Performance Optimization

Performance-optimized plugin implementation:

function debouncePlugin({ store }) {
  const debouncedActions = {}
  
  return {
    // Cleanup logic when the plugin is unloaded
    dispose() {
      Object.values(debouncedActions).forEach(clearTimeout)
    },
    
    // Wrap action
    wrapAction(actionName, action) {
      return function(...args) {
        if (debouncedActions[actionName]) {
          clearTimeout(debouncedActions[actionName])
        }
        
        return new Promise((resolve) => {
          debouncedActions[actionName] = setTimeout(async () => {
            const result = await action.call(store, ...args)
            resolve(result)
          }, 300)
        })
      }
    }
  }
}

Difference Between Plugins and Middleware

Although similar, Pinia plugins and middleware are fundamentally different:

// Middleware-style implementation
function middlewarePlugin({ store }) {
  const next = store.$onAction
  
  store.$onAction = (callback) => {
    next((context) => {
      // Pre-processing
      console.log('Before action:', context.name)
      
      // Execute original callback
      const result = callback(context)
      
      // Post-processing
      console.log('After action:', context.name)
      
      return result
    })
  }
}

Plugin Testing Strategy

Testing methods to ensure plugin quality:

import { setActivePinia, createPinia } from 'pinia'
import { describe, it, expect, beforeEach } from 'vitest'

describe('loggerPlugin', () => {
  beforeEach(() => {
    setActivePinia(createPinia().use(loggerPlugin))
  })
  
  it('should log action calls', () => {
    const consoleSpy = vi.spyOn(console, 'log')
    const store = useSomeStore()
    
    store.someAction()
    
    expect(consoleSpy).toHaveBeenCalledWith(
      expect.stringContaining('someAction')
    )
  })
})

Plugin Publishing and Sharing

Structure for publishing a plugin to npm:

my-pinia-plugin/
├── src/
│   ├── index.ts       # Main entry file
│   ├── types.ts       # TypeScript type definitions
│   └── utils.ts       # Utility functions
├── tests/            # Test files
├── package.json
├── README.md
└── tsconfig.json

Key package.json configuration:

{
  "name": "my-pinia-plugin",
  "version": "1.0.0",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "peerDependencies": {
    "pinia": "^2.0.0",
    "vue": "^3.0.0"
  }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.