Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态
与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的规范,提供了 Composition-API 风格的 API,最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持
Pinia 和 Vuex 的区别
- 在Pinia中mutations不再存在
- 在Pinia中没有modules模块概念,没有命名空间模块
- Pinia采用扁平化架构,可以说所有Store都是命名空间
- Pinia中state可直接修改或通过action,Vuex中只能通过mutations
- Pinia和Vuex中action相同,都可以执行异步逻辑,不同的是,Pinia在action中可修改state,Vuex必须使用mutations修改
Pinia使用
在main.js中引用
import { createPinia } from 'pinia'
app.use(createPinia())
pinia创建
<!-- store/user.js -->
import { defineStore } from "pinia";
const useUser = defineStore("user", {
state: () => ({ counter: 0 }),
actions: {
increment() {
this.counter++;
},
},
getters: {
doubleCount: (state) => state.counter * 2,
}
})
在 <script setup> 中使用 Pinia
// 导入
import { useTestStore } from '@/store/modules/test'
// 创建 store 实例
const testStore = useTestStore()
// 获取 state 中的值
testStore.counter
// 调用 actions 中的方法
testStore.increment()
// 获取 getters 中的值
testStore.doubleCount
注意:在 setup() 中调用 useTestStore() 之前不会创建对应的 store
Store说明
Store 是使用 defineStore() 定义的,并且它需要一个唯一名称,作为第一个参数传递:
import { defineStore } from 'pinia'
// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main', {
// other options...
})
这个 name,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools。 将返回的函数命名为 use… 是跨可组合项的约定,以使其符合你的使用习惯。
使用 store
我们正在 定义 一个 store,因为在 setup() 中调用 useStore() 之前不会创建 store:
import { useStore } from '@/stores/counter'
export default {
setup() {
const store = useStore()
return {
// 您可以返回整个 store 实例以在模板中使用它
store,
}
},
}
一旦 store 被实例化,你就可以直接在 store 上访问 state、getters 和 actions 中定义的任何属性。 我们将在接下来的页面中详细介绍这些内容,但自动补全会对您有所帮助。
请注意,store 是一个用reactive 包裹的对象,这意味着不需要在getter 之后写.value,但是,就像setup 中的props 一样,我们不能对其进行解构:
export default defineComponent({
setup() {
const store = useStore()
// 这不起作用,因为它会破坏响应式
// 这和从 props 解构是一样的
const { name, doubleCount } = store
name // "eduardo"
doubleCount // 2
return {
// 一直会是 "eduardo"
name,
// 一直会是 2
doubleCount,
// 这将是响应式的
doubleValue: computed(() => store.doubleCount),
}
},
})
为了从 Store 中提取属性同时保持其响应式,您需要使用storeToRefs()。 它将为任何响应式属性创建 refs。 当您仅使用 store 中的状态但不调用任何操作时,这很有用:
import { storeToRefs } from 'pinia'
export default defineComponent({
setup() {
const store = useStore()
// `name` 和 `doubleCount` 是响应式引用
// 这也会为插件添加的属性创建引用
// 但跳过任何 action 或 非响应式(不是 ref/reactive)的属性
const { name, doubleCount } = storeToRefs(store)
return {
name,
doubleCount
}
},
})
state 说明
修改 state 几种方式
- 直接修改 如:testStore.counter = 20
- 通过调用 action 方法修改
- 使用 testStore.$patch 修改
$patch 具体使用
$patch 传递对象
调用 $patch 方法,它允许您使用部分 state 对象同时应用多个更改,如:
testStore.$patch({
counter: store.counter + 1,
name: 'Abalam',
})
但是上面这种语法在处理 集合修改 时都需要重新创建一个新集合,因此 $patch 方法也接受一个函数来批量修改集合内部分对象的情况
$patch 传递函数
testStore.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
重置状态
通过调用 store 上的 $reset() 方法将状态 重置 到其初始值:
const store = useStore()
store.$reset()
替换state
您可以通过将其 $state 属性设置为新对象来替换 Store 的整个状态:
store.$state = { counter: 666, name: 'Paimon' }
您还可以通过更改 pinia 实例的 state 来替换应用程序的整个状态。 这在 SSR for hydration 期间使用。
<!-- pinia 实例在 main.js 文件内 -->
pinia.state.value = {}
订阅状态
可以通过 store 的 $subscribe() 方法查看状态及其变化,类似于 Vuex 的 subscribe 方法。与常规的 watch() 相比,使用 $subscribe() 的优点是 subscriptions 只会在 patches 之后触发一次(例如,使用 $patch 的函数版本时),其实 通过 watch 监听时,也是只触发一次,但在应用加载时会先执行一次,而且需要监听 pinia 实例上所有 state (pinia实例在 main.ts 内)
cartStore.$subscribe((mutation, state) => {
// import { MutationType } from 'pinia'
// type:用于记录数据通过什么方式变化的
// “direct” :通过 action 改变的
// “patch object“ :通过 $patch 传递对象的方式改变的
// “patch function” :通过 $patch 传递函数的方式改变的
mutation.type // 'direct' | 'patch object' | 'patch function'
// 与 cartStore.$id 相同
mutation.storeId // 'cart'
// 仅适用于 mutation.type === 'patch object'
mutation.payload // 补丁对象传递给 to cartStore.$patch()
// 每当它发生变化时,将整个状态持久化到本地存储
localStorage.setItem('cart', JSON.stringify(state))
})
注意: 通过 $subscribe 无法准确的知道修改的是哪个state数据
默认情况下,state subscriptions 绑定到添加它们的组件(如果 store 位于组件的 setup() 中)。 意思是,当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将 { detached: true } 作为第二个参数传递给 detach 当前组件的 state subscription:
export default {
setup() {
const someStore = useSomeStore()
// 此订阅将在组件卸载后保留
someStore.$subscribe(callback, { detached: true })
// ...
},
}
watch 监听 state 数据变化
pinia 实例在 main.js 文件内,监听的是所有的 state
<!-- pinia 实例在 main.js 文件内 -->
watch(
pinia.state,
(state) => {
// 每当它发生变化时,将整个状态持久化到本地存储
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
)
Actions说明
Actions 相当于组件中的 methods。可以通过 this 访问到整个 Store 实例,并提供完整类型(自动完成)支持。并且 actions 可以是异步的
- Actions 像 methods 一样被调用
- 可执行 同步、异步 方法
- 可以返回 Promise 对象
访问其他 store 操作
import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
// ...
}),
actions: {
async fetchUserPreferences(preferences) {
const auth = useAuthStore()
// ...
},
},
})
订阅 Actions
可以使用 store.$onAction() 订阅 action 及其结果。传递给它的回调在 action 之前执行。after 处理 Promise 并允许您在 action 完成后执行函数。以类似的方式,onError 允许您在处理中抛出错误。这些对于在运行时跟踪错误很有用,这是一个在运行 action 之前和它们 resolve/reject 之后记录的示例
注意
- $onAction 只有在调用 action 内函数时才会执行
- $subscribe 在 state 中数据变化时执行
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()
默认情况下,action subscriptions 绑定到添加它们的组件(如果 store 位于组件的 setup() 内)。 意思是,当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将 true 作为第二个参数传递给当前组件的 detach action subscription:
export default {
setup() {
const someStore = useSomeStore()
// 此订阅将在组件卸载后保留
someStore.$onAction(callback, true)
// ...
},
}
Getters 说明
Getter 完全等同于 Store 状态的计算值,具有缓存功能。可以通过 this 访问到整个 Store 的实例
将参数传递给 getter
Getters 只是幕后的 computed 属性,因此无法向它们传递任何参数。但是,您可以从 getter 返回一个函数以接受任何参数:
export const useTestStore = defineStore("testStore", {
state: () => ({
list: [
{ id: 10, active: true },
{ id: 20, active: false },
],
}),
getters: {
getUserById: (state) => (userId) => state.list.find((item) => item.id === userId)
},
})
并在组件中使用:
<script setup>
import { useTestStore } from '@/store/modules/test'
// 创建 store 实例
const testStore = useTestStore()
// 根据 Id 获取指定用户
testStore.getUserById(10)
</script>
请注意,在执行上述操作时,getter 不再换成,它们只是您调用的函数。但是可以在 getter 本身内部汉城一些结果,这并不常见,但应该证明性能更高:
export const useTestStore = defineStore("testStore", {
getters: {
// 根据用户Id从活跃的用户中获取指定用户
getActiveUserById(state) {
// 先缓存 活跃的 用户
const activeUsers = state.list.filter((user) => user.active);
return (userId) => activeUsers.find((user) => user.id === userId);
},
},
})
访问其他 Store 的 getter
要使用其他存储 getter, 您可以直接在 getter 内部使用它:
import { useOtherStore } from './other-store'
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
// 获取其他 stroe 实例
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
Pinia 插件
使用 pinia.use() 将插件添加到 pinia 实例中。 最简单的例子是通过返回一个对象为所有Store添加一个静态属性:
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方式获取,使用storeToRefs则无效,但使用toRefs(store) 则可以
store.secret // 'the cake is a lie'
这对于添加全局对象(如路由器、模式或 toast 管理器)很有用
在插件中调用 $subscribe
您也可以在插件中使用 store.$subscribe 和 store.$onAction
pinia.use(({ store }) => {
store.$subscribe(() => {
// 在存储变化的时候执行
})
store.$onAction(() => {
// 在 action 的时候执行
})
})
注意:pinia-plugin-persistedstate 库就是利用 $subscribe 选项
Pinia 持久化存储插件 pinia-plugin-persistedstate 使用
1: 安装插件
yarn add pinia-plugin-persistedstate
2: 添加插件到Pinia中
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
3: 将持久选项 persist 添加到要持久化的Store上
import { defineStore } from 'pinia'
export const useStore = defineStore('store', {
state: () => {
return {
someState: 'hello pinia',
}
},
persist: true,
})
说明:只需要在 defineStore 中添加 persist: true,就开启了把 state 数据存储到本地的功能
注意:默认情况下
- 存储整个 state 数据
- 使用 localStorage 存储到本地
- 存储的 key 为 store.$id 的名字
persist配置
您可以通过指定persist属性的选项来配置存储的持久化方式
import { defineStore } from 'pinia'
export const useStore = defineStore('store', {
state: () => {
return {
save: {
me: 'saved',
notMe: 'not-saved',
},
someState: 'hello pinia',
}
},
persist: {
key: 'my-custom-key', // 默认 store.$id 作为名字,如上面就是 store
storage: sessionStorage, // 使用 session 存储
paths: ['save.me', 'someState'], // 存储 state 中的save.me 和 someState 属性
},
})
paths说明
- [] 表示没有持久化状态即什么都不存储,未定义或null表示整个状态都被持久化
- paths 中的值代表需要存储的数据,未写的属性不会存储
- [‘save.me’] 代表只存储 state 中 save对象 内的 me 属性
注意:另外 persist 对象中还有 serializer、beforeRestore、afterRestore 配置属性,但不会经常用到
引用
https://pinia.web3doc.top/getting-started.html