Development of Pinia plugins
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:
store.$onAction
- Listen for action callsstore.$subscribe
- Listen for state changesstore.$patch
- Modify statestore.$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
上一篇:Store间相互调用
下一篇:服务端渲染支持