Appearance
插件
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 创建过程 实现属性注入:
- 调用
pinia.use()
时,插件函数被存入队列。 - 每次创建 Store 时,Pinia 会依次执行插件函数,合并返回的对象到 Store 实例。
- 最终 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,
},
}
)