Skip to content

插件

Pinia store 现在完全支持扩展。以下是你可以扩展的内容:

  • 为 store 添加新的属性
  • 定义 store 时增加新的选项
  • 为 store 增加新的方法
  • 包装现有的方法
  • 改变甚至取消 action
  • 实现副作用,如本地存储
  • 仅应用插件于特定 store

插件是通过 pinia.use() 添加到 pinia 实例的。最简单的例子是通过返回一个对象将一个静态属性添加到所有 store。

js
import { createPinia } from "pinia";

// 创建的每个 store 中都会添加一个名为 `secret` 的属性。
// 在安装此插件后,插件可以保存在不同的文件中
function SecretPiniaPlugin() {
  return { secret: "the cake is a lie" };
}

const pinia = createPinia();
// 将该插件交给 Pinia
pinia.use(SecretPiniaPlugin);

// 在另一个文件中
const store = useStore();
store.secret; // 'the cake is a lie'

简介

Pinia 插件是一个函数,可以选择性地返回要添加到 store 的属性。它接收一个可选参数,即 context

js
export function myPiniaPlugin(context) {
  context.pinia; // 用 `createPinia()` 创建的 pinia。
  context.app; // 用 `createApp()` 创建的当前应用(仅 Vue 3)。
  context.store; // 该插件想扩展的 store
  context.options; // 定义传给 `defineStore()` 的 store 的可选对象。
  // ...
}

然后用 pinia.use() 将这个函数传给 pinia

js
pinia.use(myPiniaPlugin);

插件只会应用于pinia 传递给应用后创建的 store,否则它们不会生效。

典型使用场景

场景1:注入共享工具方法

js
// 插件:为所有 Store 添加 $logger 方法
pinia.use(() => ({
  $logger(msg) {
    console.log(`[Store Log]: ${msg}`)
  }
}))

// 任意 Store 中直接使用
const useUserStore = defineStore('user', {
  actions: {
    fetchUser() {
      this.$logger('Fetching user...') // 输出: [Store Log]: Fetching user...
    }
  }
})

场景2:统一附加配置

js
// 插件:为所有 Store 添加 API 客户端
pinia.use(() => ({
  $api: axios.create({ baseURL: 'https://api.example.com' })
}))

// Store 中调用接口
const usePostStore = defineStore('post', {
  actions: {
    async fetchPosts() {
      const res = await this.$api.get('/posts') // 直接使用注入的 $api
      return res.data
    }
  }
})

场景3:跨 Store 共享状态​

js
// 插件:注入全局共享的计数器
pinia.use(() => ({
  globalCounter: ref(0)
}))

// Store A 修改计数器
const storeA = useStoreA()
storeA.globalCounter.value++

// Store B 读取同一计数器
const storeB = useStoreB()
console.log(storeB.globalCounter.value) // 输出: 1

技术实现原理

Pinia 插件通过 ​拦截 Store 创建过程​ 实现属性注入:

  1. 调用 pinia.use() 时,插件函数被存入队列
  2. 每次创建 Store 时,Pinia 会依次执行插件函数,合并返回的对象到 Store 实例。
  3. 最终 Store 包含原生状态 + 插件注入的属性

添加新的外部属性

当添加外部属性、第三方库的类实例或非响应式的简单值时,你应该先用 markRaw() 来包装一下它,再将它传给 pinia。下面是一个在每个 store 中添加路由器的例子:

js
import { markRaw } from 'vue'
// 根据你的路由器的位置来调整
import { router } from './router'

pinia.use(({ store }) => {
  store.router = markRaw(router)
})

在插件中调用 $subscribe

js
pinia.use(({ store }) => {
  store.$subscribe(() => {
    // 响应 store 变化
  })
  store.$onAction(() => {
    // 响应 store actions
  })
})

添加新的选项

在定义 store 时,可以创建新的选项,以便在插件中使用它们。例如,你可以创建一个 debounce 选项,允许你让任何 action 实现防抖。

js
defineStore('search', {
  actions: {
    searchContacts() {
      // ...
    },
  },

  // 这将在后面被一个插件读取
  debounce: {
    // 让 action searchContacts 防抖 300ms
    searchContacts: 300,
  },
})

然后,该插件可以读取该选项来包装 action,并替换原始 action:

js
// 使用任意防抖库
import debounce from 'lodash/debounce'

pinia.use(({ options, store }) => {
  if (options.debounce) {
    // 我们正在用新的 action 来覆盖这些 action
    return Object.keys(options.debounce).reduce((debouncedActions, action) => {
      debouncedActions[action] = debounce(
        store[action],
        options.debounce[action]
      )
      return debouncedActions
    }, {})
  }
})

注意,在使用 setup 语法时,自定义选项作为第 3 个参数传递:

js
defineStore(
  'search',
  () => {
    // ...
  },
  {
    // 这将在后面被一个插件读取
    debounce: {
      // 让 action searchContacts 防抖 300ms
      searchContacts: 300,
    },
  }
)