Pinia插件开发
Pinia插件的基本概念
Pinia插件是扩展Pinia功能的强大工具,允许开发者在store的生命周期中注入自定义逻辑。插件本质上是一个函数,接收context对象作为参数,可以访问store实例和app实例。
import { createPinia } from 'pinia'
const pinia = createPinia()
function myPlugin({ store, app }) {
// 插件逻辑
}
pinia.use(myPlugin)
插件开发的核心API
Pinia插件主要依赖以下几个核心API:
store.$onAction
- 监听action调用store.$subscribe
- 监听state变化store.$patch
- 修改statestore.$state
- 访问整个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)
})
})
}
开发状态持久化插件
一个常见的插件用例是实现状态持久化。下面是一个完整的localStorage持久化插件实现:
function persistPlugin({ store }) {
// 从localStorage加载初始状态
const savedState = localStorage.getItem(`pinia-${store.$id}`)
if (savedState) {
store.$patch(JSON.parse(savedState))
}
// 监听state变化并保存
store.$subscribe((mutation, state) => {
localStorage.setItem(`pinia-${store.$id}`, JSON.stringify(state))
})
}
// 使用插件
const pinia = createPinia()
pinia.use(persistPlugin)
开发多标签页同步插件
在多个浏览器标签页间同步Pinia状态是另一个实用场景:
function syncTabsPlugin({ store }) {
// 监听storage事件
window.addEventListener('storage', (event) => {
if (event.key === `pinia-${store.$id}`) {
store.$patch(JSON.parse(event.newValue))
}
})
// 保存状态变化
store.$subscribe((mutation, state) => {
localStorage.setItem(`pinia-${store.$id}`, JSON.stringify(state))
})
}
开发请求缓存插件
对于需要频繁请求数据的store,可以开发缓存插件:
function cachePlugin({ store }) {
const cache = new Map()
// 包装原始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
}
})
}
插件中的依赖注入
Pinia插件可以利用Vue的provide/inject机制来共享功能:
function apiPlugin({ app }) {
const api = axios.create({
baseURL: 'https://api.example.com'
})
app.provide('api', api)
}
// 在组件中使用
export default {
inject: ['api'],
methods: {
async fetchData() {
const response = await this.api.get('/data')
// ...
}
}
}
开发TypeScript支持的插件
为插件添加TypeScript类型支持可以提升开发体验:
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}`)
}
}
}
插件组合与执行顺序
多个插件可以组合使用,执行顺序与注册顺序一致:
const pinia = createPinia()
// 先执行loggerPlugin,再执行persistPlugin
pinia.use(loggerPlugin)
pinia.use(persistPlugin)
// 也可以链式调用
pinia
.use(loggerPlugin)
.use(persistPlugin)
开发Devtools集成插件
为Pinia store添加开发工具支持:
function devtoolsPlugin({ store, app }) {
if (process.env.NODE_ENV === 'development') {
store._devtools = {
id: store.$id,
state: store.$state,
actions: Object.keys(store.$actions)
}
// 发送到Vue Devtools
app.config.globalProperties.$devtools?.send('pinia:store-init', store._devtools)
}
}
插件中的错误处理
健壮的插件应该包含错误处理机制:
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 // 可以选择重新抛出或处理错误
}
}
})
}
插件性能优化
考虑性能的插件实现方式:
function debouncePlugin({ store }) {
const debouncedActions = {}
return {
// 插件卸载时的清理逻辑
dispose() {
Object.values(debouncedActions).forEach(clearTimeout)
},
// 包装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)
})
}
}
}
}
插件与中间件的区别
虽然类似,但Pinia插件与中间件有本质区别:
// 中间件风格的实现
function middlewarePlugin({ store }) {
const next = store.$onAction
store.$onAction = (callback) => {
next((context) => {
// 前置处理
console.log('Before action:', context.name)
// 执行原始回调
const result = callback(context)
// 后置处理
console.log('After action:', context.name)
return result
})
}
}
插件测试策略
确保插件质量的测试方法:
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')
)
})
})
插件发布与共享
准备发布到npm的插件结构:
my-pinia-plugin/
├── src/
│ ├── index.ts # 主入口文件
│ ├── types.ts # TypeScript类型定义
│ └── utils.ts # 工具函数
├── tests/ # 测试文件
├── package.json
├── README.md
└── tsconfig.json
package.json关键配置:
{
"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间相互调用
下一篇:服务端渲染支持