Pinia
起始于 2019 年 11 月左右的一次试验,其目标是设计一个领有组合式 API 的 Vue 状态治理库。同时反对 Vue 2 和 Vue 3,并且不强制要求开发者应用组合式 API。
pinia 官网链接地址。
Pinia
是 Vue 的专属状态治理库,它容许你跨组件或页面共享状态。对于单页利用来说,能够通过一行简略的 export const state = reactive({})
来共享一个全局状态。如果利用在服务器端渲染,这可能会使你的利用暴露出一些安全漏洞。
如果应用 Pinia
,即便在 小型单页利用
中,你也能够取得如下性能:
-
Devtools
反对- 追踪
actions
、mutations
的工夫线 - 在组件中展现它们所用到的
Store
- 让调试更容易的
Time travel
- 追踪
-
热更新
不用重载页面
即可批改 Store- 开发时可放弃以后的 State
- 插件:可通过插件扩大
Pinia
性能 - 为 JS 开发者提供适当的 TypeScript 反对以及主动补全性能。
- 反对服务端渲染
根底示例
上面就是 pinia API 的根本用法。你能够先创立一个 Store:
// stores/counter.js
import {defineStore} from 'pinia'
export const useCounterStore = defineStore('counter', {state: () => {return { count: 0}
},
// 也能够这样定义
// state: () => ({ count: 0})
actions: {increment() {this.count++},
},
})
而后你就能够在一个组件中 应用该 store
了:
// 须要应用的页面
import {useCounterStore} from '@/stores/counter'
export default {setup() {const counter = useCounterStore()
counter.count++
// 带有主动补全
counter.$patch({count: counter.count + 1})
// 或者应用 action 代替
counter.increment()},
}
为实现更多高级用法,你甚至能够应用一个函数 (与组件 setup() 相似) 来定义一个 Store:
export const useCounterStore = defineStore('counter', () => {const count = ref(0)
function increment() {count.value++}
return {count, increment}
})
如果你还不相熟 setup() 函数和组合式 API,别放心,Pinia 也提供了一组相似 Vuex 的 映射 state 的辅助函数。你能够用和之前一样的形式来定义 Store,而后通过 mapStores()、mapState() 或 mapActions() 拜访
const useCounterStore = defineStore('counter', {state: () => ({count: 0}),
getters: {double: (state) => state.count * 2,
},
actions: {increment() {this.count++},
},
})
const useUserStore = defineStore('user', {// ...})
export default {
computed: {
// 其余计算属性
// ...
// 容许拜访 this.counterStore 和 this.userStore
...mapStores(useCounterStore, useUserStore)
// 容许读取 this.count 和 this.double
...mapState(useCounterStore, ['count', 'double']),
},
methods: {// 容许读取 this.increment()
...mapActions(useCounterStore, ['increment']),
},
}
你将会在外围概念局部理解到更多对于每个 映射辅助函数
的信息。
比照 Vuex
Pinia 起源于一次摸索 Vuex 下一个迭代的试验,因而联合了 Vuex 5 外围团队探讨中的许多想法。最初,咱们意识到 Pinia 曾经实现了咱们在 Vuex 5 中想要的大部分性能,所以决定将其作为新的举荐计划来代替 Vuex。
与 Vuex 相比,Pinia 不仅提供了一个更简略的 API,也提供了合乎组合式 API 格调的 API,最重要的是,搭配 TypeScript 一起应用时有十分牢靠的类型推断反对。
比照 Vuex 3.x/4.x
Vuex 3.x 只适配 Vue 2,而 Vuex 4.x 是适配 Vue 3 的。
Pinia API 与 Vuex(<=4) 也有很多不同,即:
mutation 已被弃用
。它们常常被认为是极其冗余
的。它们初衷是带来 devtools 的集成计划,但这已不再是一个问题了。- 无须要创立自定义的简单包装器来反对 TypeScript,所有都可标注类型,API 的设计形式是尽可能地利用 TS 类型推理。
- 无过多的魔法字符串注入,只须要导入函数并调用它们,而后享受主动补全的乐趣就好。
- 无须要动静增加 Store,它们
默认都是动静的
,甚至你可能都不会留神到这点。留神,你依然能够在任何时候手动应用一个 Store 来注册它,但因为它是主动的,所以你不须要放心它。 - 不再有嵌套构造的
模块
。你依然能够通过导入和应用另一个 Store 来隐含地嵌套 stores 空间,尽管是 Pinia 从设计上提供的是一个扁平的构造,但依然可能在 Store 之间进行穿插组合。你甚至能够让 Stores 有循环依赖关系
。 - 不再有
可命名的模块
。思考到 Store 的扁平架构,Store 的命名取决于它们的定义形式,你甚至能够说所有 Store 都应该命名。
装置
用你喜爱的包管理器装置 pinia:
yarn add pinia
# 或者应用 npm
npm install pinia
TIP
如果你的利用应用的是 Vue 2,你还须要装置组合式 API 包:@vue/composition-api
。如果你应用的是 Nuxt,你应该参考这篇指南。
如果你正在应用 Vue CLI,你能够试试这个非官方插件。
创立一个 pinia 实例 (根 store) 并将其传递给利用:
import {createApp} from 'vue'
import {createPinia} from 'pinia' // 引入 pinia
import App from './App.vue'
const app = createApp(App)
app.use(createPinia()) // 应用 pinia
app.mount('#app')
如果你应用的是 Vue 2,你还须要装置一个插件,并在利用的根部注入创立的 pinia:
import {createPinia, PiniaVuePlugin} from 'pinia'
Vue.use(PiniaVuePlugin)
const pinia = createPinia()
new Vue({
el: '#app',
// 其余配置...
// ...
// 请留神,同一个 `pinia' 实例
// 能够在同一个页面的多个 Vue 利用中应用。pinia,
})
因而不能与 Vuex 一起应用
这样能力提供 devtools 的反对。在 Vue 3 中,一些性能依然不被反对,如 time traveling 和编辑,这是因为 vue-devtools 还没有相干的 API,但 devtools 也有很多针对 Vue 3 的专属性能,而且就开发者的体验来说,Vue 3 整体上要好得多。在 Vue 2 中,Pinia 应用的是 Vuex 的现有接口 ( 因而不能与 Vuex 一起应用
)。
Store 是什么?
Store (如 Pinia)
是一个 保留状态
和业务逻辑的实体
,它 并不与你的组件树绑定
。换句话说,它 承载着全局状态
。它有点像一个永远存在的组件,每个组件都能够读取和写入它。它有三个概念,state
、getter
和 action
,咱们能够假如这些概念相当于组件中的 data
、computed
和 methods
。
应该在什么时候应用 Store?
一个 Store 应该蕴含能够在整个利用中拜访的数据。这包含在许多中央应用的数据,例如显示在导航栏中的 用户信息
,以及须要通过页面保留的数据,例如 一个非常复杂的多步骤表单
。
另一方面,你应该 防止在 Store 中引入那些本来能够在组件中保留的本地数据
,例如, 一个元素在页面中的可见性
。
并非所有的利用都须要拜访全局状态
,但如果你的利用的确须要一个全局状态,那 Pinia 将使你的开发过程更轻松。
Pinia 外围概念
Store
定义 Store
在深入研究外围概念之前,咱们得晓得 Store 是用 defineStore()
定义的,它的第一个参数要求是一个 举世无双
的名字:
import {defineStore} from 'pinia'
// 你能够对 `defineStore()` 的返回值进行任意命名,但最好应用 store 的名字,同时以 `use` 结尾且以 `Store` 结尾。(比方 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的利用中 Store 的惟一 ID。export const useStore = defineStore('main', {// 其余配置...})
这个 名字
,也被用作 id,是 必须传入的
,Pinia 将用它来连贯 store 和 devtools。为了养成习惯性的用法,将返回的函数命名为 use… 是一个合乎 组合式函数格调
的约定。
defineStore() 的第二个参数可承受 两类值
:Setup 函数
或 Option 对象
。
Option Store
与 Vue 的选项式 API 相似,咱们也能够传入一个带有 state
、actions
与 getters
属性的 Option 对象
export const useCounterStore = defineStore('counter', {state: () => ({count: 0}),
getters: {double: (state) => state.count * 2,
},
actions: {increment() {this.count++},
},
})
你能够认为 state 是 store 的数据 (data)
,getters 是 store 的计算属性 (computed)
,而 actions 则是办法 (methods)
。
为不便上手应用,Option Store
应尽可能 直观简略
。
Setup Store
也存在另一种定义 store 的可用语法。与 Vue 组合式 API 的 setup 函数 类似,咱们能够传入 一个函数
,该函数定义了一些 响应式属性
和办法
,并且返回一个带有咱们想裸露进来的属性和办法的对象。
export const useCounterStore = defineStore('counter', () => {const count = ref(0)
function increment() {count.value++}
return {count, increment}
})
在 Setup Store
中:
ref()
就是state 属性
computed()
就是getters
function()
就是actions
Setup store 比 Option Store 带来了更多的灵活性,因为你能够在一个 store 内创立侦听器,并自在地应用任何组合式函数。不过,请记住,应用组合式函数会让 SSR 变得更加简单。
应该选用哪种语法?
和在 Vue 中如何抉择组合式 API 与选项式 API 一样,抉择 你感觉最舒服的那一个就好
。如果你还不确定,能够先试试 Option Store
。
应用 Store
尽管咱们后面定义了一个 store
,但在 setup() 调用 useStore() 之前
,store 实例是不会被创立的:
import {useCounterStore} from '@/stores/counter'
export default {setup() {const store = useCounterStore()
return {
// 为了能在模板中应用它,你能够返回整个 Store 实例。store,
}
},
}
你能够 定义任意多的 store
,但为了 让应用 pinia 的好处最大化
(比方容许构建工具主动进行代码宰割以及 TypeScript 推断),你应该 在不同的文件中去定义 store
。
如果你还不会应用 setup 组件,你也能够通过 映射辅助函数
来应用 Pinia。
一旦 store 被实例化,你能够间接拜访在 store 的 state、getters 和 actions 中定义的任何属性。咱们将在后续章节持续理解这些细节,目前主动补全将帮忙你应用相干属性。
请留神,store 是一个用 reactive 包装的对象,这意味着 不须要在 getters 前面写 .value
,就像 setup 中的 props 一样,如果你写了,咱们也不能解构它
:
export default defineComponent({setup() {const store = useCounterStore()
// ❌ 这将无奈失效,因为它毁坏了响应性
// 这与从 `props` 中解构是一样的。const {name, doubleCount} = store
name // "eduardo"
doubleCount // 2
return {
// 始终是 "eduardo"
name,
// 始终是 2
doubleCount,
// 这个将是响应式的
doubleValue: computed(() => store.doubleCount),
}
},
})
为了从 store 中提取属性时放弃其响应性,你须要应用 storeToRefs()
。它将为每一个 响应式属性
创立援用。当你只应用 store 的状态而不调用任何 action 时,它会十分有用。请留神,你能够间接从 store 中解构 action,因为它们也被绑定到 store 上:
import {storeToRefs} from 'pinia'
export default defineComponent({setup() {const store = useCounterStore()
// `name` and `doubleCount` 都是响应式 refs
// 这也将为由插件增加的属性创立 refs
// 同时会跳过任何 action 或非响应式 (非 ref/ 响应式) 属性
const {name, doubleCount} = storeToRefs(store)
// 名为 increment 的 action 能够间接提取
const {increment} = store
return {
name,
doubleCount,
increment,
}
},
})
State
在大多数状况下,state 都是你的 store 的外围
。人们通常会先定义能代表他们 APP 的 state。在 Pinia 中,state 被定义为一个返回初始状态的函数
。这使得 Pinia 能够同时反对 服务端
和客户端
。
import {defineStore} from 'pinia'
const useStore = defineStore('storeId', {
// 为了残缺类型推理,举荐应用箭头函数
state: () => {
return {
// 所有这些属性都将主动推断出它们的类型
count: 0,
name: 'Eduardo',
isAdmin: true,
items: [],
hasChanged: true,
}
},
})
TIP
如果你应用的是 Vue 2,你在 state 中创立的数据与 Vue 实例中的 data 遵循同样的规定,即 state 对象必须是清晰的,当你想向其增加新属性时,你须要调用 Vue.set()。参考:Vue#data。
拜访 state
默认状况下,你能够通过 store
实例拜访 state,间接对其进行读写
。
const store = useStore()
store.count++
重置 state
你能够通过调用 store 的 $reset() 办法将 state 重置为初始值。
const store = useStore()
store.$reset()
应用选项式 API 的用法
在上面的例子中,你能够假如相干 store 曾经创立了:
/ 示例文件门路:// ./src/stores/counter.js
import {defineStore} from 'pinia'
const useCounterStore = defineStore('counter', {state: () => ({count: 0,}),
})
如果你不能应用组合式 API,但你能够应用 computed
,methods
,…,那你能够应用 mapState()
辅助函数将 state 属性映射为只读的计算属性:
import {mapState} from 'pinia'
import {useCounterStore} from '../stores/counter'
export default {
computed: {
// 能够拜访组件中的 this.count
// 与从 store.count 中读取的数据雷同
...mapState(useCounterStore, ['count'])
// 与上述雷同,但将其注册为 this.myOwnName
...mapState(useCounterStore, {
myOwnName: 'count',
// 你也能够写一个函数来取得对 store 的拜访权
double: store => store.count * 2,
// 它能够拜访 `this`,但它没有标注类型...
magicValue(store) {return store.someGetter + this.count + this.double},
}),
},
}
可批改的 state
如果你想 批改这些 state 属性
(例如,如果你有一个表单),你能够应用 mapWritableState()
作为代替。但留神你不能像 mapState() 那样传递一个函数:
import {mapWritableState} from 'pinia'
import {useCounterStore} from '../stores/counter'
export default {
computed: {
// 能够拜访组件中的 this.count,并容许设置它。// this.count++
// 与从 store.count 中读取的数据雷同
...mapWritableState(useCounterStore, ['count'])
// 与上述雷同,但将其注册为 this.myOwnName
...mapWritableState(useCounterStore, {myOwnName: 'count',}),
},
}
TIP
对于像 数组
这样的汇合,你并不一定须要应用 mapWritableState()
,mapState()
也容许你调用汇合上的办法,除非你想用 cartItems = [] 替换整个数组。
变更 state
除了用 store.count++ 间接扭转 store
,你还能够调用 $patch 办法
。它容许你用一个 state 的补丁对象在 同一时间更改多个属性
:
store.$patch({
count: store.count + 1,
age: 120,
name: 'DIO',
})
不过,用这种语法的话,有些变更真的很难实现或者很耗时
:任何汇合的批改(例如,向数组中增加、移除一个元素或是做 splice 操作)都须要你创立一个新的汇合。因而,$patch 办法
也承受 一个函数
来组合这种难以用补丁对象实现的变更。
store.$patch((state) => {state.items.push({ name: 'shoes', quantity: 1})
state.hasChanged = true
})
两种变更 store 办法的次要区别是
$patch()
容许你将多个变更纳入 devtools 的同一个条目中。同时请留神,间接批改
state,$patch() 也会呈现在 devtools 中,而且能够进行 time travel(在 Vue 3 中还没有)。
替换 state
你 不能齐全替换掉
store 的 state,因为那样 会毁坏其响应性
。然而,你能够 patch 它。
// 这实际上并没有替换 `$state`
store.$state = {count: 24}
// 在它外部调用 `$patch()`:store.$patch({count: 24})
你也能够通过变更 pinia 实例的 state 来设置整个利用的初始 state。这罕用于 SSR 中的激活过程。
pinia.state.value = {}
订阅 state
相似于 Vuex 的 subscribe
办法,你能够通过 store 的 $subscribe()
办法侦听 state 及其变动。比起一般的 watch(),应用 $subscribe() 的益处是 subscriptions 在 patch 后只触发一次
(例如,当应用下面的函数版本时)。
cartStore.$subscribe((mutation, state) => {// import { MutationType} from 'pinia'
mutation.type // 'direct' | 'patch object' | 'patch function'
// 和 cartStore.$id 一样
mutation.storeId // 'cart'
// 只有 mutation.type === 'patch object' 的状况下才可用
mutation.payload // 传递给 cartStore.$patch() 的补丁对象。// 每当状态发生变化时,将整个 state 长久化到本地存储。localStorage.setItem('cart', JSON.stringify(state))
})
默认状况下,state subscription 会被绑定到增加它们的组件上(如果 store 在组件的 setup() 外面)。这意味着,当该组件被卸载时,它们将被主动删除。如果你想在组件卸载后仍旧保留它们,请将 {detached: true} 作为第二个参数,以将 state subscription 从以后组件中拆散:
export default {setup() {const someStore = useSomeStore()
// 在组件被卸载后,该订阅依旧会被保留。someStore.$subscribe(callback, { detached: true})
// ...
},
}
TIP
你能够在 pinia 实例上侦听整个 state。
watch(
pinia.state,
(state) => {
// 每当状态发生变化时,将整个 state 长久化到本地存储。localStorage.setItem('piniaState', JSON.stringify(state))
},
{deep: true}
)
Getter
Getter 齐全等同于 store 的 state 的计算值。能够通过 defineStore()
中的 getters 属性来定义它们。举荐应用箭头函数
,并且 它将接管 state 作为第一个参数
:
export const useStore = defineStore('main', {state: () => ({count: 0,}),
getters: {doubleCount: (state) => state.count * 2,
},
})
大多数时候,getter 仅依赖 state,不过,有时它们也可能会应用其余 getter。因而,即便在应用惯例函数定义 getter 时,咱们也能够通过 this 拜访到 整个 store 实例
,但(在 TypeScript 中) 必须定义返回类型。这是为了防止 TypeScript 的已知缺点,不过这不影响用箭头函数定义的 getter,也不会影响不应用 this 的 getter。
export const useStore = defineStore('main', {state: () => ({count: 0,}),
getters: {
// 主动推断出返回类型是一个 number
doubleCount(state) {return state.count * 2},
// 返回类型 ** 必须 ** 明确设置
doublePlusOne(): number {
// 整个 store 的 主动补全和类型标注 ✨
return this.doubleCount + 1
},
},
})
而后你能够 间接拜访 store 实例上的 getter
了:
<template>
<p>Double count is {{store.doubleCount}}</p>
</template>
<script>
export default {setup() {const store = useStore()
return {store}
},
}
</script>
拜访其余 getter
与计算属性一样,你也能够组合多个 getter。通过 this,你能够拜访到其余任何 getter
。即便你没有应用 TypeScript,你也能够用 JSDoc 来让你的 IDE 提醒类型。
export const useStore = defineStore('main', {state: () => ({count: 0,}),
getters: {
// 类型是主动推断进去的,因为咱们没有应用 `this`
doubleCount: (state) => state.count * 2,
// 这里咱们须要本人增加类型(在 JS 中应用 JSDoc)
// 能够用 this 来援用 getter
/**
* 返回 count 的值乘以 2 加 1
*
* @returns {number}
*/
doubleCountPlusOne() {
// 主动补全
return `this`.doubleCount + 1
},
},
})
向 getter 传递参数
Getter 只是幕后的 计算
属性,所以 不能够向它们传递任何参数
。不过,你能够 从 getter 返回一个函数
,该函数能够承受任意参数:
export const useStore = defineStore('main', {
getters: {getUserById: (state) => {return (userId) => state.users.find((user) => user.id === userId)
},
},
})
并在组件中应用
<script>
export default {setup() {const store = useStore()
return {getUserById: store.getUserById}
},
}
</script>
<template>
<p>User 2: {{getUserById(2) }}</p>
</template>
请留神,当你这样做时,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)
},
},
})
拜访其余 store 的 getter
想要应用另一个 store 的 getter 的话,那就间接在 getter 内应用就好:
import {useOtherStore} from './other-store'
export const useStore = defineStore('main', {state: () => ({// ...}),
getters: {otherGetter(state) {const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
应用 setup()时的用法
作为 store 的一个属性,你能够间接拜访任何 getter(与 state 属性齐全一样):
export default {setup() {const store = useStore()
store.count = 3
store.doubleCount // 6
},
}
应用选项式 API 的用法
在上面的例子中,你能够假如相干的 store 曾经创立了:
// 示例文件门路:// ./src/stores/counter.js
import {defineStore} from 'pinia'
export const useCounterStore = defineStore('counter', {state: () => ({count: 0,}),
getters: {doubleCount(state) {return state.count * 2},
},
})
应用 setup()
尽管并不是每个开发者都会应用组合式 API,但 setup() 钩子仍旧能够使 Pinia 在选项式 API 中更易用。并且不须要额定的映射辅助函数!
import {useCounterStore} from '../stores/counter'
export default {setup() {const counterStore = useCounterStore()
return {counterStore}
},
computed: {quadrupleCounter() {return this.counterStore.doubleCount * 2},
},
}
不应用 setup()
你能够应用前一节的 state 中的 mapState()
函数来将其映射为 getters:
import {mapState} from 'pinia'
import {useCounterStore} from '../stores/counter'
export default {
computed: {
// 容许在组件中拜访 this.doubleCount
// 与从 store.doubleCount 中读取的雷同
...mapState(useCounterStore, ['doubleCount']),
// 与上述雷同,但将其注册为 this.myOwnName
...mapState(useCounterStore, {
myOwnName: 'doubleCount',
// 你也能够写一个函数来取得对 store 的拜访权
double: store => store.doubleCount,
}),
},
}
Action
Action 相当于组件中的 method
。它们能够通过 defineStore() 中的 actions 属性来定义
,并且它们也是定义业务逻辑的完满抉择。
export const useStore = defineStore('main', {state: () => ({count: 0,}),
actions: {increment() {this.count++},
randomizeCounter() {this.count = Math.round(100 * Math.random())
},
},
})
相似 getter,action 也可 通过 this 拜访整个 store 实例
,并反对残缺的类型标注(以及主动补全)。不同的是,action 能够是异步的
,你能够在它们外面 await
调用任何 API,以及其余 action!上面是一个应用 Mande 的例子。请留神,你应用什么库并不重要,只有你失去的是一个 Promise
, 你甚至能够 (在浏览器中) 应用原生 fetch 函数
:
import {mande} from 'mande'
const api = mande('/api/users')
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
}
},
},
})
你也齐全能够自在地设置任何你想要的参数以及返回任何后果。当调用 action
时,所有类型也都是能够被主动推断进去的。
Action 能够像办法一样被调用:
export default defineComponent({setup() {const main = useMainStore()
// 作为 store 的一个办法调用该 action
main.randomizeCounter()
return {}},
})
拜访其余 store 的 action
想要应用另一个 store 的话,那你间接在 action 中调用就好了:
import {useAuthStore} from './auth-store'
export const useSettingsStore = defineStore('settings', {state: () => ({
preferences: null,
// ...
}),
actions: {async fetchUserPreferences() {const auth = useAuthStore()
if (auth.isAuthenticated) {this.preferences = await fetchPreferences()
} else {throw new Error('User must be authenticated')
}
},
},
})
应用 setup()时的用法
你能够将任何 action 作为 store 的一个办法间接调用:
export default {setup() {const store = useStore()
store.randomizeCounter()},
}
应用选项式 API 的用法
在上面的例子中,你能够假如相干的 store 曾经创立了:
// 示例文件门路:// ./src/stores/counter.js
import {defineStore} from 'pinia',
const useCounterStore = defineStore('counter', {state: () => ({count: 0}),
actions: {increment() {this.count++}
}
})
应用 setup()
尽管并不是每个开发者都会应用组合式 API,但 setup() 钩子仍旧能够使 Pinia 在选项式 API 中更易用。并且不须要额定的映射辅助函数!
import {useCounterStore} from '../stores/counter'
export default {setup() {const counterStore = useCounterStore()
return {counterStore}
},
methods: {incrementAndPrint() {this.counterStore.increment()
console.log('New Count:', this.counterStore.count)
},
},
}
不应用 setup()
如果你不喜爱应用组合式 API,你也能够应用 mapActions()
辅助函数将 action 属性映射为你组件中的办法。
import {mapActions} from 'pinia'
import {useCounterStore} from '../stores/counter'
export default {
methods: {// 拜访组件内的 this.increment()
// 与从 store.increment() 调用雷同
...mapActions(useCounterStore, ['increment'])
// 与上述雷同,但将其注册为 this.myOwnName()
...mapActions(useCounterStore, { myOwnName: 'doubleCount'}),
},
}
订阅 action
你能够通过 store.$onAction()
来监听 action 和它们的后果。传递给它的回调函数会在 action 自身之前执行。after 示意在 promise 解决之后,容许你在 action 解决后执行一个一个回调函数。同样地,onError 容许你在 action 抛出谬误或 reject 时执行一个回调函数。这些函数对于追踪运行时谬误十分有用,相似于 Vue docs 中的这个提醒。
这里有一个例子,在运行 action 之前以及 action resolve/reject 之后打印日志记录。
const unsubscribe = someStore.$onAction(
({
name, // action 名称
store, // store 实例,相似 `someStore`
args, // 传递给 action 的参数数组
after, // 在 action 返回或解决后的钩子
onError, // action 抛出或回绝的钩子
}) => {
// 为这个特定的 action 调用提供一个共享变量
const startTime = Date.now()
// 这将在执行 "store" 的 action 之前触发。console.log(`Start "${name}" with params [${args.join(',')}].`)
// 这将在 action 胜利并齐全运行后触发。// 它期待着任何返回的 promise
after((result) => {
console.log(`Finished "${name}" after ${Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// 如果 action 抛出或返回一个回绝的 promise,这将触发
onError((error) => {
console.warn(`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// 手动删除监听器
unsubscribe()
默认状况下,action 订阅器会被绑定到增加它们的组件上(如果 store 在组件的 setup() 内)。这意味着,当该组件被卸载时,它们将被主动删除。如果你想在组件卸载后仍旧保留它们,请将 true 作为第二个参数传递给 action 订阅器,以便将其从以后组件中拆散:
export default {setup() {const someStore = useSomeStore()
// 在组件被卸载后,这个订阅依旧会被保留。someStore.$onAction(callback, true)
// ...
},
}
优雅解决 pinia 数据长久化问题
pinia 和 vuex 都有一个通病,就是数据长久化须要 手动批改
,插件自身不具备数据长久化的能力,当页面刷新或者利用更新后所有的状态数据均会失落。
pinia 是反对插件的,不晓得各位小伙伴看过没有,这里援用的 pinia 数据长久化插件是pinia-plugin-persistedstate
。
装置:pinia-plugin-persistedstate
pnpm : pnpm i pinia-plugin-persistedstate
npm : npm i pinia-plugin-persistedstate
yarn : yarn add pinia-plugin-persistedstate
配置插件 pinia-plugin-persistedstate
在 src 目录下新增 store 文件夹,新增 index.js
// pinia 数据长久化存储
import {createPinia} from "pinia"
import {createPersistedState} from 'pinia-plugin-persistedstate'
const store = createPinia()
store.use(createPersistedState({
serializer: { // 指定参数序列化器
serialize: JSON.stringify,
deserialize: JSON.parse,
}
}))
export default store
在 src 新增 store 文件夹下,新增 main.ts
import {defineStore} from "pinia"
export const useMainStore = defineStore({
id: 'main',
state: () => ({name: 'hello pinia'}),
// persist: true, 开启数据长久化,所有 store 数据将被长久化到指定仓库
persist: { // 自定义数据长久化形式
// key: 'store-key', 指定 key 进行存储,此时非 key 的值不会长久化,刷新就会失落
storage: window.localStorage, // 指定换成地址
// paths: ['nested.data'],// 指定须要长久化的 state 的门路名称
beforeRestore: context => {console.log('Before' + context)
},
afterRestore: context => {console.log('After'+ context)
},
},
getters: {getName: (state) => {return state.name}
},
actions: {SET_NAME (param:string) {this.name = param}
}
})
再将 store 下的 index.ts 引入到入口文件 main.ts 文件里
import {createApp} from 'vue'
import App from './App.vue'
// 注入路由
import router from './router'
// 注入 store
import store from './store'
import 'element-plus/dist/index.css'
// 导入 element-plus 音讯 api
import {ElMessage} from "element-plus"
import {currentTiemStr} from "@/script/utils"
const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
// 全局罕用 api 配置
// 音讯提醒 APi
app.config.globalProperties.$ElMessage = ElMessage
// 获取工夫戳办法
app.config.globalProperties.$CurrentTiemStr = currentTiemStr
应用办法还是同上述 1 是一样的,当变动 name 值后,你刷新数据,会发现便后的 name 值始终都是最新值,至此,pinia 应用插件 pinia-plugin-persistedstate
pina 应用举例
定义 stores/index.js
// stores/index.js
// pinia 数据长久化存储
import {createPinia} from "pinia"
import {createPersistedState} from 'pinia-plugin-persistedstate'
const store = createPinia()
store.use(createPersistedState({
serializer: { // 指定参数序列化器
serialize: JSON.stringify,
deserialize: JSON.parse,
}
}))
export default store
定义 stores/user.js
// stores/user.js
import {defineStore} from 'pinia'
export const useUserInfoStore = defineStore('userInfo', {state: () => {
return {username: 'Eduardo',}
},
// persist: true, 开启数据长久化,所有 store 数据将被长久化到指定仓库
persist: { // 自定义数据长久化形式
// key: 'store-key', 指定 key 进行存储,此时非 key 的值不会长久化,刷新就会失落
storage: window.localStorage, // 指定换成地址
// paths: ['nested.data'],// 指定须要长久化的 state 的门路名称
beforeRestore: context => {console.log('Before' + context)
},
afterRestore: context => {console.log('After' + context)
},
},
getters: {},
actions: {logout() {
this.$patch({
name: '',
isAdmin: false,
})
// we could do other stuff like redirecting the user
},
/**
* Attempt to login a user
*/
addRegister(data) {
const userData = data
this.username = userData.username
},
},
})
组件内应用
援用 state 值
// 援用
import {useUserInfoStore} from '@/stores/userInfo.js'
// 获取值
const getUserInfo = useUserInfoStore()
// 间接应用
const userInfo = reactive(getUserInfo);
批改 state 值
// 援用 userInfo.js
import {useUserInfoStore} from '@/stores/userInfo.js'
const userInfo = useUserInfoStore()
const submitForm = async () => {
getApi({
path: 'users/register',
method: 'post',
params: {
'username': ruleForm.username,
'pwd': ruleForm.pass
}
}).then(res => {console.log('res')
if (res.data.code == 200) {
const userData = res.data.data
// 增加数据
userInfo.addRegister(userData)
// 页面跳转
router.push('Admin')
} else {
ElMessage({
message: 'res.data.msg',
grouping: true,
type: 'error',
})
}
})
}