阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > Pinia插件开发

Pinia插件开发

作者:陈川 阅读数:24886人阅读 分类: Vue.js

Pinia插件的基本概念

Pinia插件是扩展Pinia功能的强大工具,允许开发者在store的生命周期中注入自定义逻辑。插件本质上是一个函数,接收context对象作为参数,可以访问store实例和app实例。

import { createPinia } from 'pinia'

const pinia = createPinia()

function myPlugin({ store, app }) {
  // 插件逻辑
}

pinia.use(myPlugin)

插件开发的核心API

Pinia插件主要依赖以下几个核心API:

  1. store.$onAction - 监听action调用
  2. store.$subscribe - 监听state变化
  3. store.$patch - 修改state
  4. store.$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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌