Pinia 插件系统 #
概述 #
Pinia 插件是一个函数,可以用来扩展每个 Store 的功能。插件可以在 Store 创建时添加新属性、修改现有行为或响应特定事件。
基本用法 #
注册插件 #
ts
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
// 注册插件
pinia.use(({ store }) => {
console.log(`Store created: ${store.$id}`)
})
const app = createApp(App)
app.use(pinia)
插件结构 #
ts
interface PiniaPluginContext {
pinia: Pinia
app: App
store: Store
options: DefineStoreOptions
}
function myPlugin(context: PiniaPluginContext) {
const { pinia, app, store, options } = context
// 扩展 store
}
扩展 Store #
添加属性 #
ts
// 添加全局属性
pinia.use(({ store }) => {
store.$name = 'My Store'
})
// 在组件中使用
const userStore = useUserStore()
console.log(userStore.$name) // 'My Store'
添加方法 #
ts
pinia.use(({ store }) => {
store.$log = function() {
console.log(`[${store.$id}]`, this.$state)
}
})
// 使用
userStore.$log()
添加计算属性 #
ts
import { computed } from 'vue'
pinia.use(({ store }) => {
// 添加计算属性
store.$isEmpty = computed(() => Object.keys(store.$state).length === 0)
})
内置钩子 #
$subscribe #
监听状态变化:
ts
pinia.use(({ store }) => {
store.$subscribe((mutation, state) => {
console.log('State changed:', mutation.type)
console.log('New state:', state)
})
})
$onAction #
监听 action 调用:
ts
pinia.use(({ store }) => {
store.$onAction(({ name, args, after, onError }) => {
console.log(`Action ${name} called with:`, args)
after((result) => {
console.log(`Action ${name} returned:`, result)
})
onError((error) => {
console.error(`Action ${name} failed:`, error)
})
})
})
实用插件示例 #
1. 日志插件 #
ts
// plugins/logger.ts
import type { PiniaPluginContext } from 'pinia'
export function loggerPlugin({ store }: PiniaPluginContext) {
// 记录 action 调用
store.$onAction(({ name, args, after, onError }) => {
const startTime = Date.now()
console.log(`[${store.$id}] ${name} started`, args)
after((result) => {
const duration = Date.now() - startTime
console.log(`[${store.$id}] ${name} finished in ${duration}ms`, result)
})
onError((error) => {
console.error(`[${store.$id}] ${name} failed`, error)
})
})
// 记录状态变化
store.$subscribe((mutation, state) => {
console.log(`[${store.$id}] State changed via ${mutation.type}`, state)
})
}
ts
// main.ts
import { loggerPlugin } from './plugins/logger'
const pinia = createPinia()
pinia.use(loggerPlugin)
2. 重置插件 #
ts
// plugins/reset.ts
import type { PiniaPluginContext } from 'pinia'
export function resetPlugin({ store }: PiniaPluginContext) {
// 保存初始状态
const initialState = JSON.parse(JSON.stringify(store.$state))
// 添加重置方法
store.$resetTo = function(path?: string) {
if (path) {
this.$patch({ [path]: initialState[path] })
} else {
this.$patch(initialState)
}
}
}
3. 加载状态插件 #
ts
// plugins/loading.ts
import { ref } from 'vue'
import type { PiniaPluginContext } from 'pinia'
export function loadingPlugin({ store }: PiniaPluginContext) {
const loadingActions = new Set<string>()
store.$onAction(({ name, after, onError }) => {
// 只处理异步 action
loadingActions.add(name)
store.loading = true
after(() => {
loadingActions.delete(name)
if (loadingActions.size === 0) {
store.loading = false
}
})
onError(() => {
loadingActions.delete(name)
if (loadingActions.size === 0) {
store.loading = false
}
})
})
}
4. API 请求插件 #
ts
// plugins/api.ts
import type { PiniaPluginContext } from 'pinia'
interface ApiOptions {
baseUrl: string
headers?: Record<string, string>
}
export function apiPlugin(options: ApiOptions) {
return ({ store }: PiniaPluginContext) => {
store.$api = {
async get(endpoint: string) {
const response = await fetch(`${options.baseUrl}${endpoint}`, {
headers: options.headers
})
return response.json()
},
async post(endpoint: string, data: any) {
const response = await fetch(`${options.baseUrl}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...options.headers
},
body: JSON.stringify(data)
})
return response.json()
}
}
}
}
ts
// main.ts
import { apiPlugin } from './plugins/api'
const pinia = createPinia()
pinia.use(apiPlugin({
baseUrl: 'https://api.example.com',
headers: {
'Authorization': 'Bearer token'
}
}))
ts
// stores/user.ts
export const useUserStore = defineStore('user', {
actions: {
async fetchUsers() {
this.users = await this.$api.get('/users')
}
}
})
5. 验证插件 #
ts
// plugins/validation.ts
import type { PiniaPluginContext } from 'pinia'
interface ValidationRule {
validate: (value: any) => boolean
message: string
}
export function validationPlugin({ store, options }: PiniaPluginContext) {
const rules = options.validation as Record<string, ValidationRule[]> | undefined
if (!rules) return
store.$validate = function() {
const errors: Record<string, string[]> = {}
for (const [field, fieldRules] of Object.entries(rules)) {
const value = this[field]
const fieldErrors: string[] = []
for (const rule of fieldRules) {
if (!rule.validate(value)) {
fieldErrors.push(rule.message)
}
}
if (fieldErrors.length > 0) {
errors[field] = fieldErrors
}
}
return Object.keys(errors).length === 0 ? null : errors
}
}
ts
// stores/user.ts
export const useUserStore = defineStore('user', {
state: () => ({
email: '',
age: 0
}),
validation: {
email: [
{ validate: (v) => !!v, message: 'Email is required' },
{ validate: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), message: 'Invalid email' }
],
age: [
{ validate: (v) => v >= 0, message: 'Age must be positive' },
{ validate: (v) => v <= 150, message: 'Invalid age' }
]
}
})
6. 历史记录插件 #
ts
// plugins/history.ts
import { ref } from 'vue'
import type { PiniaPluginContext } from 'pinia'
export function historyPlugin({ store }: PiniaPluginContext) {
const history = ref<any[]>([])
const currentIndex = ref(-1)
const maxHistory = 50
// 记录状态变化
store.$subscribe((mutation, state) => {
// 移除当前位置之后的历史
history.value = history.value.slice(0, currentIndex.value + 1)
// 添加新状态
history.value.push(JSON.parse(JSON.stringify(state)))
// 限制历史长度
if (history.value.length > maxHistory) {
history.value.shift()
}
currentIndex.value = history.value.length - 1
})
// 添加导航方法
store.$undo = function() {
if (currentIndex.value > 0) {
currentIndex.value--
this.$patch(history.value[currentIndex.value])
}
}
store.$redo = function() {
if (currentIndex.value < history.value.length - 1) {
currentIndex.value++
this.$patch(history.value[currentIndex.value])
}
}
store.$canUndo = () => currentIndex.value > 0
store.$canRedo = () => currentIndex.value < history.value.length - 1
}
插件配置 #
条件应用 #
ts
pinia.use(({ store, options }) => {
// 只对特定 store 应用插件
if (options.persist) {
// 持久化逻辑
}
})
插件选项 #
ts
interface MyPluginOptions {
prefix?: string
debug?: boolean
}
export function myPlugin(options: MyPluginOptions = {}) {
const { prefix = '', debug = false } = options
return ({ store }: PiniaPluginContext) => {
if (debug) {
console.log(`${prefix}Store created: ${store.$id}`)
}
}
}
// 使用
pinia.use(myPlugin({ prefix: '[Pinia]', debug: true }))
插件最佳实践 #
1. 类型安全 #
ts
import type { PiniaPluginContext, Store } from 'pinia'
declare module 'pinia' {
interface PiniaCustomProperties {
$log: () => void
$api: {
get: (endpoint: string) => Promise<any>
post: (endpoint: string, data: any) => Promise<any>
}
}
}
export function myPlugin({ store }: PiniaPluginContext) {
store.$log = function() {
console.log(this.$state)
}
}
2. 清理资源 #
ts
export function myPlugin({ store }: PiniaPluginContext) {
const interval = setInterval(() => {
console.log('Polling...')
}, 1000)
// 返回清理函数
return () => {
clearInterval(interval)
}
}
3. 错误处理 #
ts
export function myPlugin({ store }: PiniaPluginContext) {
store.$onAction({
onError(error) {
// 统一错误处理
console.error('Action error:', error)
// 可以发送到错误追踪服务
}
})
}
下一步 #
现在你已经掌握了 Pinia 插件系统,接下来让我们学习状态持久化。
- 状态持久化 - 本地存储状态
最后更新:2026-03-28