1、概述
- Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。
1)对比 Vuex
- 更小,只有1kb;
- 更简单,actions 可以同时处理同步任务和异步任务;
- 不再有命名空间模块,但是各个 store 之间相互独立并且可以互相引用。
- 组合式 API 风格和良好的 TS 支持。
2)安装
Vue3 创建一个 pinia(根存储)并将其传递给应用程序
import { createPinia } from 'pinia' app.use(createPinia())
- Vue 2,您还需要安装一个插件并将创建的
pinia
注入应用程序的根目录 - 需要安装组合 API:
@vue/composition-api
import { createPinia, PiniaVuePlugin } from 'pinia'
Vue.use(PiniaVuePlugin)
const pinia = createPinia()
new Vue({
el: '#app',
// ...
pinia,
})
2、store
1)定义
Store 是使用
defineStore()
定义的,并且它需要一个唯一名称,作为第一个参数传递- 这个 name,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools。
import { defineStore } from 'pinia' // useStore 可以是 useUser、useCart 之类的任何东西 // 第一个参数是应用程序中 store 的唯一 id export const useStore = defineStore('main', { // other options... })
2)模块化
- 可以根据需要定义任意数量的 store ,并且您应该在不同的文件中定义每个 store 以充分利用 pinia。
3、state
在 Pinia 中,状态被定义为返回初始状态的函数。
import { defineStore } from 'pinia' const useStore = defineStore('storeId', { // 推荐使用 完整类型推断的箭头函数 state: () => { return { // 所有这些属性都将自动推断其类型 counter: 0, name: 'Eduardo', isAdmin: true, } }, })
1)访问 “state”
- 默认情况下,您可以通过
store
实例访问状态来直接读取和写入状态
const store = useStore()
store.counter++
2)改变状态
除了直接用
store.counter++
修改 store,你还可以调用$patch
方法。 它允许您使用部分“state”对象同时应用多个更改。store.$patch({ counter: store.counter + 1, name: 'Abalam', })
$patch
方法也接受一个函数来批量修改集合内部分对象的情况:cartStore.$patch((state) => { state.items.push({ name: 'shoes', quantity: 1 }) state.hasChanged = true })
3)重置状态
- 可以通过调用 store 上的
$reset()
方法将状态 重置 到其初始值
const store = useStore()
store.$reset()
4)替换state
可以通过将其
$state
属性设置为新对象来替换 Store 的整个状态:store.$state = { counter: 666, name: 'Paimon' }
5)订阅状态
- 可以通过 store 的
$subscribe()
方法查看状态及其变化。 - 与常规的
watch()
相比,使用$subscribe()
的优点是 subscriptions 只会在 patches 之后触发一次。 当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将
{ detached: true }
作为第二个参数传递给 detach 当前组件的 state subscription:export default { setup() { const someStore = useSomeStore() // 此订阅将在组件卸载后保留 someStore.$subscribe(callback, { detached: true }) // ... }, }
4、getters
Getter 完全等同于 Store 状态 的 computed
- 箭头函数接收 state 作为第一个参数。
- 常规函数通过 this 访问到整个 store 实例,但是需要定义返回类型。
export const useStore = defineStore('main', { state: () => ({ counter: 0, }), getters: { // 自动将返回类型推断为数字 doubleCount(state) { return state.counter * 2 }, // 返回类型必须明确设置 doublePlusOne(): number { return this.counter * 2 + 1 }, }, })
1)访问其他 getter
- 与计算属性一样,您可以组合多个 getter。 通过
this
访问任何其他 getter。
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
getters: {
// 类型是自动推断的,因为我们没有使用 `this`
doubleCount: (state) => state.counter * 2,
doubleCountPlusOne() {
return this.doubleCount + 1
},
},
})
2)将参数传递给 getter
Getters 只是幕后的 computed 属性,因此无法向它们传递任何参数。 但是,您可以从 getter 返回一个函数以接受任何参数:
export const useStore = defineStore('main', { getters: { getUserById: (state) => { return (userId) => state.users.find((user) => user.id === userId) }, }, }) // App.vue
User 2: {{ getUserById(2) }}
在执行此操作时,getter 不再缓存,它们只是您调用的函数。 但是,您可以在 getter 本身内部利用闭包缓存一些结果。
export const useStore = defineStore('main', { getters: { getActiveUserById(state) { const activeUsers = state.users.filter((user) => user.active) return (userId) => activeUsers.find((user) => user.id === userId) }, }, })
3)访问其他 Store 的getter
- 要使用其他存储 getter,您可以直接在 getter 内部使用它
import { useOtherStore } from './other-store'
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
5、actions
- Actions 相当于组件中的 methods,可以通过 this 访问到整个 store 实例。
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
actions: {
increment() {
this.counter++
},
randomizeCounter() {
this.counter = Math.round(100 * Math.random())
},
},
})
actions
可以是异步的,您可以在其中await
任何 API 调用甚至其他操作!
export const useUsers = defineStore('users', {
state: () => ({
userData: null,
// ...
}),
actions: {
async registerUser(login, password) {
try {
this.userData = await api.post({ login, password })
showTooltip(`Welcome back ${this.userData.name}!`)
} catch (error) {
showTooltip(error)
// 让表单组件显示错误
return error
}
},
},
})
1)访问其他 store 操作
- 要使用另一个 store ,您可以直接在 action 内部使用它:
import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
// ...
}),
actions: {
async fetchUserPreferences(preferences) {
const auth = useAuthStore()
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
})
2)订阅 Actions
- 可以使用
store.$onAction()
订阅 action 及其结果。 传递给它的回调在 action 之前执行。 after
处理 Promise 并允许您在 action 完成后执行函数。onError
允许您在处理中抛出错误。 这些对于在运行时跟踪错误很有用 。
const unsubscribe = someStore.$onAction(
({
name, // action 的名字
store, // store 实例
args, // 调用这个 action 的参数
after, // 在这个 action 执行完毕之后,执行这个函数
onError, // 在这个 action 抛出异常的时候,执行这个函数
}) => {
// 记录开始的时间变量
const startTime = Date.now()
// 这将在 `store` 上的操作执行之前触发
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// 如果 action 成功并且完全运行后,after 将触发。
// 它将等待任何返回的 promise
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// 如果 action 抛出或返回 Promise.reject ,onError 将触发
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// 手动移除订阅
unsubscribe()
当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将
true
作为第二个参数传递给当前组件的 detach action subscription:export default { setup() { const someStore = useSomeStore() // 此订阅将在组件卸载后保留 someStore.$onAction(callback, true) // ... }, }
6、plugins
- 使用
pinia.use()
将插件添加到 pinia 实例中。 - 安装此插件后创建的每个store添加一个名为
secret
的属性
import { createPinia } from 'pinia'
// 这可能在不同的文件中
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:
export function myPiniaPlugin(context) {
context.pinia // 使用 `createPinia()` 创建的 pinia
context.app // 使用 `createApp()` 创建的当前应用程序(仅限 Vue 3)
context.store // 插件正在扩充的 store
context.options // 定义存储的选项对象传递给`defineStore()`
// ...
}
1)添加新的外部属性
当添加外部属性、来自其他库的类实例或仅仅是非响应式的东西时,您应该在将对象传递给 pinia 之前使用
markRaw()
包装对象。 这是一个将路由添加到每个 store 的示例:import { markRaw } from 'vue' // 根据您的路由所在的位置进行调整 import { router } from './router' pinia.use(({ store }) => { store.router = markRaw(router) })
2)在插件中调用 $subscribe
您也可以在插件中使用 store.subscribe和store.onAction
pinia.use(({ store }) => { store.$subscribe(() => { // 在存储变化的时候执行 }) store.$onAction(() => { // 在 action 的时候执行 }) })
3)引入新的 store 属性
向 store 添加新属性时,您还应该扩展
PiniaCustomProperties
接口。import 'pinia' declare module 'pinia' { export interface PiniaCustomProperties { // 通过使用 setter,我们可以同时允许字符串和引用 set hello(value: string | Ref
) get hello(): string // 你也可以定义更简单的值 simpleNumber: number } } 然后可以安全地写入和读取它:
pinia.use(({ store }) => { store.hello = 'Hola' store.hello = ref('Hola') store.number = Math.random() // @ts-expect-error: we haven't typed this correctly store.number = ref(Math.random()) })