乐趣区

关于前端:vue中如何优雅高效的使用pinia

Pinia 起始于 2019 年 11 月左右的一次试验,其目标是设计一个领有组合式 API 的 Vue 状态治理库。同时反对 Vue 2 和 Vue 3,并且不强制要求开发者应用组合式 API。

pinia 官网链接地址。

Pinia 是 Vue 的专属状态治理库,它容许你跨组件或页面共享状态。对于单页利用来说,能够通过一行简略的 export const state = reactive({}) 来共享一个全局状态。如果利用在服务器端渲染,这可能会使你的利用暴露出一些安全漏洞。

如果应用 Pinia,即便在 小型单页利用 中,你也能够取得如下性能:

  • Devtools 反对

    • 追踪 actionsmutations 的工夫线
    • 在组件中展现它们所用到的 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) 是一个 保留状态 业务逻辑的实体 ,它 并不与你的组件树绑定 。换句话说,它 承载着全局状态。它有点像一个永远存在的组件,每个组件都能够读取和写入它。它有三个概念,stategetteraction,咱们能够假如这些概念相当于组件中的 datacomputedmethods

应该在什么时候应用 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 相似,咱们也能够传入一个带有 stateactionsgetters 属性的 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,但你能够应用 computedmethods,…,那你能够应用 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',
            })
        }
    })
}
退出移动版