Skip to content

插件 API 参考

本节提供了 Pinia 插件系统的完整 API 参考文档。

核心插件 API

pinia.use()

注册插件到 Pinia 实例。

ts
function use(plugin: PiniaPlugin): Pinia

参数

  • plugin: PiniaPlugin - 要注册的插件函数

返回值

  • 类型: Pinia
  • 描述: Pinia 实例(支持链式调用)

示例

js
import { createPinia } from 'pinia'
import myPlugin from './my-plugin'

const pinia = createPinia()
pinia.use(myPlugin)

// 链式调用
pinia
  .use(plugin1)
  .use(plugin2)
  .use(plugin3)

插件类型定义

PiniaPlugin

插件函数的类型定义。

ts
type PiniaPlugin = (context: PiniaPluginContext) => 
  Partial<PiniaCustomProperties & PiniaCustomStateProperties> | void

参数

  • context: PiniaPluginContext - 插件上下文对象

返回值

  • 类型: Partial<PiniaCustomProperties & PiniaCustomStateProperties> | void
  • 描述: 要添加到 store 的属性,或 void

PiniaPluginContext

插件上下文接口。

ts
interface PiniaPluginContext<
  Id extends string = string,
  S extends StateTree = StateTree,
  G extends _GettersTree<S> = _GettersTree<S>,
  A extends _ActionsTree = _ActionsTree
> {
  pinia: Pinia
  app: App
  store: Store<Id, S, G, A>
  options: DefineStoreOptionsInPlugin<Id, S, G, A>
}

属性

  • pinia: Pinia - 当前 Pinia 实例
  • app: App - Vue 应用实例
  • store: Store<Id, S, G, A> - 当前 store 实例
  • options: DefineStoreOptionsInPlugin<Id, S, G, A> - store 定义选项

插件上下文属性

context.pinia

当前的 Pinia 实例。

ts
const pinia: Pinia

示例

js
function myPlugin({ pinia }) {
  // 访问 Pinia 实例
  console.log('Pinia 实例:', pinia)
  
  // 访问全局状态
  console.log('全局状态:', pinia.state.value)
  
  // 访问所有 stores
  console.log('所有 stores:', pinia._s)
}

context.app

Vue 应用实例。

ts
const app: App

示例

js
function myPlugin({ app }) {
  // 访问 Vue 应用实例
  console.log('Vue 应用:', app)
  
  // 访问应用配置
  console.log('应用配置:', app.config)
  
  // 注册全局组件
  app.component('MyComponent', MyComponent)
}

context.store

当前 store 实例。

ts
const store: Store<Id, S, G, A>

示例

js
function myPlugin({ store }) {
  // 访问 store 属性
  console.log('Store ID:', store.$id)
  console.log('Store 状态:', store.$state)
  
  // 监听状态变化
  store.$subscribe((mutation, state) => {
    console.log('状态变化:', mutation, state)
  })
  
  // 监听 action 执行
  store.$onAction((action) => {
    console.log('Action 执行:', action)
  })
}

context.options

store 定义选项。

ts
const options: DefineStoreOptionsInPlugin<Id, S, G, A>

示例

js
function myPlugin({ options }) {
  // 访问 store 定义选项
  console.log('Store ID:', options.id)
  console.log('State 函数:', options.state)
  console.log('Getters:', options.getters)
  console.log('Actions:', options.actions)
  
  // 检查自定义选项
  if (options.persist) {
    console.log('启用持久化:', options.persist)
  }
}

插件返回值

添加属性到 Store

插件可以返回对象来添加属性到 store。

js
function addPropertiesPlugin() {
  return {
    // 添加响应式属性
    hello: ref('world'),
    
    // 添加计算属性
    doubled: computed(() => store.count * 2),
    
    // 添加方法
    reset() {
      store.$reset()
    },
    
    // 添加异步方法
    async fetchData() {
      const data = await api.getData()
      store.data = data
    }
  }
}

添加状态属性

js
function addStatePlugin({ store }) {
  return {
    // 添加到状态
    $state: {
      createdAt: new Date(),
      version: '1.0.0'
    }
  }
}

常用插件模式

日志插件

js
function createLoggerPlugin(options = {}) {
  return function loggerPlugin({ store }) {
    const { logActions = true, logMutations = true } = options
    
    if (logActions) {
      store.$onAction(({ name, args, after, onError }) => {
        console.log(`🚀 Action "${name}" 开始执行`, args)
        
        after((result) => {
          console.log(`✅ Action "${name}" 执行完成`, result)
        })
        
        onError((error) => {
          console.error(`❌ Action "${name}" 执行失败`, error)
        })
      })
    }
    
    if (logMutations) {
      store.$subscribe((mutation, state) => {
        console.log(`🔄 状态变化:`, mutation)
        console.log(`📊 新状态:`, state)
      })
    }
  }
}

// 使用
pinia.use(createLoggerPlugin({
  logActions: true,
  logMutations: false
}))

持久化插件

js
function createPersistedStatePlugin(options = {}) {
  return function persistedStatePlugin({ store, options: storeOptions }) {
    // 检查是否启用持久化
    if (!storeOptions.persist) return
    
    const {
      key = store.$id,
      storage = localStorage,
      paths = null
    } = typeof storeOptions.persist === 'object' 
      ? storeOptions.persist 
      : {}
    
    // 恢复状态
    const savedState = storage.getItem(key)
    if (savedState) {
      try {
        const parsed = JSON.parse(savedState)
        if (paths) {
          // 只恢复指定路径
          paths.forEach(path => {
            if (parsed[path] !== undefined) {
              store.$state[path] = parsed[path]
            }
          })
        } else {
          // 恢复整个状态
          store.$patch(parsed)
        }
      } catch (error) {
        console.error('恢复状态失败:', error)
      }
    }
    
    // 监听状态变化并保存
    store.$subscribe((mutation, state) => {
      try {
        const toSave = paths 
          ? paths.reduce((acc, path) => {
              acc[path] = state[path]
              return acc
            }, {})
          : state
        
        storage.setItem(key, JSON.stringify(toSave))
      } catch (error) {
        console.error('保存状态失败:', error)
      }
    })
  }
}

// 使用
pinia.use(createPersistedStatePlugin())

重置插件

js
function resetPlugin({ store }) {
  const initialState = JSON.parse(JSON.stringify(store.$state))
  
  return {
    $reset() {
      store.$patch(initialState)
    }
  }
}

// 使用
pinia.use(resetPlugin)

调试插件

js
function createDebugPlugin(options = {}) {
  return function debugPlugin({ store }) {
    if (process.env.NODE_ENV !== 'development') return
    
    const { 
      logLevel = 'info',
      enableTimeTravel = true,
      maxHistorySize = 50 
    } = options
    
    // 历史记录
    const history = []
    
    // 记录状态变化
    store.$subscribe((mutation, state) => {
      const entry = {
        timestamp: Date.now(),
        mutation,
        state: JSON.parse(JSON.stringify(state))
      }
      
      history.push(entry)
      
      // 限制历史记录大小
      if (history.length > maxHistorySize) {
        history.shift()
      }
      
      if (logLevel === 'verbose') {
        console.log('🔍 调试信息:', entry)
      }
    })
    
    return {
      // 时间旅行
      $timeTravel: enableTimeTravel ? (index) => {
        if (history[index]) {
          store.$patch(history[index].state)
        }
      } : undefined,
      
      // 获取历史记录
      $getHistory: () => [...history],
      
      // 清除历史记录
      $clearHistory: () => {
        history.length = 0
      }
    }
  }
}

// 使用
pinia.use(createDebugPlugin({
  logLevel: 'verbose',
  enableTimeTravel: true
}))

高级插件功能

插件间通信

js
// 事件总线插件
function createEventBusPlugin() {
  const eventBus = new EventTarget()
  
  return function eventBusPlugin({ store }) {
    return {
      $emit(event, data) {
        eventBus.dispatchEvent(new CustomEvent(event, { detail: data }))
      },
      
      $on(event, handler) {
        eventBus.addEventListener(event, handler)
        return () => eventBus.removeEventListener(event, handler)
      },
      
      $off(event, handler) {
        eventBus.removeEventListener(event, handler)
      }
    }
  }
}

// 使用
pinia.use(createEventBusPlugin())

// 在 store 中使用
const store = useMyStore()
store.$emit('user-updated', { id: 1, name: 'John' })
store.$on('user-updated', (event) => {
  console.log('用户更新:', event.detail)
})

条件插件

js
function createConditionalPlugin(condition, plugin) {
  return function conditionalPlugin(context) {
    if (condition(context)) {
      return plugin(context)
    }
  }
}

// 只在开发环境使用的插件
const devOnlyPlugin = createConditionalPlugin(
  () => process.env.NODE_ENV === 'development',
  debugPlugin
)

pinia.use(devOnlyPlugin)

// 只对特定 store 使用的插件
const userStoreOnlyPlugin = createConditionalPlugin(
  ({ store }) => store.$id === 'user',
  userSpecificPlugin
)

pinia.use(userStoreOnlyPlugin)

插件组合

js
function composePlugins(...plugins) {
  return function composedPlugin(context) {
    const results = plugins.map(plugin => plugin(context)).filter(Boolean)
    
    // 合并所有插件返回的属性
    return Object.assign({}, ...results)
  }
}

// 组合多个插件
const combinedPlugin = composePlugins(
  loggerPlugin,
  persistedStatePlugin,
  resetPlugin
)

pinia.use(combinedPlugin)

TypeScript 支持

插件类型定义

ts
import { PiniaPluginContext, Store } from 'pinia'

// 定义插件选项类型
interface MyPluginOptions {
  enabled?: boolean
  prefix?: string
}

// 定义插件添加的属性类型
interface MyPluginProperties {
  $myMethod: () => void
  $myProperty: string
}

// 插件函数类型
type MyPlugin = (options?: MyPluginOptions) => 
  (context: PiniaPluginContext) => MyPluginProperties

// 实现插件
const createMyPlugin: MyPlugin = (options = {}) => {
  return ({ store }) => {
    return {
      $myMethod() {
        console.log('My method called')
      },
      $myProperty: options.prefix || 'default'
    }
  }
}

扩展 Store 类型

ts
// 声明模块扩展
declare module 'pinia' {
  export interface PiniaCustomProperties {
    $myMethod: () => void
    $myProperty: string
  }
  
  export interface PiniaCustomStateProperties {
    createdAt: Date
  }
}

// 现在 TypeScript 知道这些属性存在
const store = useMyStore()
store.$myMethod() // ✅ 类型安全
store.$myProperty // ✅ 类型安全

插件最佳实践

1. 命名约定

js
// 使用 $ 前缀避免与用户属性冲突
function myPlugin() {
  return {
    $myMethod() {}, // ✅ 好的
    myMethod() {},  // ❌ 可能冲突
  }
}

2. 错误处理

js
function safePlugin({ store }) {
  try {
    // 插件逻辑
    return {
      $safeMethod() {
        try {
          // 方法实现
        } catch (error) {
          console.error('插件方法执行失败:', error)
        }
      }
    }
  } catch (error) {
    console.error('插件初始化失败:', error)
    return {}
  }
}

3. 性能考虑

js
function performantPlugin({ store }) {
  // 避免在每次状态变化时执行昂贵操作
  const debouncedSave = debounce(() => {
    // 昂贵的保存操作
  }, 1000)
  
  store.$subscribe(() => {
    debouncedSave()
  })
}

4. 清理资源

js
function resourcePlugin({ store }) {
  const interval = setInterval(() => {
    // 定期任务
  }, 5000)
  
  // 在 store 销毁时清理
  store.$dispose(() => {
    clearInterval(interval)
  })
}

相关链接

Released under the MIT License.