Pinia 大菠萝的初次认识

210 阅读5分钟

Pinia 是 Vue 的专属状态管理库,它允许我们跨组件或页面共享状态。你如果熟悉组件是 API ,就可能认为仅凭一行 export const state = reactive({}) 就能实现共享全局状态。对于单页面来说确实可行,但如果应用在服务器端渲染的话,就有可能会暴露出一些安全漏洞。如果使用 Pinia ,你可以获得以下功能:

  • 测试工具集
  • 插件:可通过插件来拓展 Pinia 的功能
  • 提供适当的 TypeScript 支持和自动补全功能
  • 支持服务端的渲染
  • Devtools 支持
    • 追踪 action、mutations 的时间线
    • 可以在组件中展示它们用到的 Store
    • 使用 Time travel ,让调试更容易
  • 热更新
    • 不需要重新加载页面也可以修改 Store
    • 开发的时候依旧可以保持当前的 State

安装

Pinia 的安装很简单,只需要在命令行输入 npm install piniayarn add pinia 就能完成安装。

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')

以上代码就是创建了一个 pinia 实例(根 store)并将其传递给应用。

定义 Store

在研究核心概念之前,我们要知道 Store 是通过 defineStore() 来定义的,它的第一个参数要求是一个独一无二的名字:

import { defineStore } from 'pinia'

// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。
// (比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useAlertsStore = defineStore('alerts', {
  // 其他配置...
})

这个名字 也被称为 id,是一定要传入的,Pinia 会利用它来连接 store 和 devtools 。为了养成习惯性用法,约定俗成地将返回函数命名为以 use... 开头。

Option Store

和 Vue 的选项式 API 类似,我们也可以传入一个带有 stateactiongetters 属性的 Option 对象

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0, name: 'Eduardo' }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

你可以简单地认为state 是 store 的数据(data),action 是方法(methods),而 getters 是 store 的计算属性(computed)。

Setup Store

Setup Store 是另一种定义 store 的语法,和 Vue 组合式 API 的 setup 函数类似,通过传入一个函数,该函数定义了一些响应式属性和方法,并返回一个我们想要展示的属性和方法的对象。

export const useCounterStore = defineStore('counter', () => {
 const count = ref(0)
 const doubleCount = computed(() => count.value * 2)
 function increment() {
   count.value++
 }

 return { count, doubleCount, increment }
})

在 Setup Store 中:

  • ref() 就是 state 属性。
  • computed() 就是 getters
  • function() 就是 actions。 需要注意的是,想要让 pinia 正确识别 state ,就必须在 setup store 中返回 state 中的所有属性。这意味着不能使用私有属性。不完整的返回会影响 SSR 、 开发工具和其他插件的正常运行。

和 Option Store 相比,Setup store 带来了更多的灵活性,因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数。不过请记住,使用组合式函数会让 SSR 变得更加复杂。

Setup store 也可以依赖于全局提供的属性,比如路由。任何应用层面提供的属性都可以在 store 中使用 inject() 访问,就像在组件中一样:

import { inject } from 'vue'
import { useRoute } from 'vue-router'
import { defineStore } from 'pinia'

export const useSearchFilters = defineStore('search-filters', () => {
  const route = useRoute()
  // 这里假定 `app.provide('appProvided', 'value')` 已经调用过
  const appProvided = inject('appProvided')

  // ...

  return {
    // ...
  }
})

注意不要返回像 route 或 appProvided (上例中)之类的属性,因为它们不属于 store,而且你可以在组件中直接用 useRoute() 和 inject('appProvided') 访问。

使用 Store

我们在前面定义了一个 store,但我们在 <script setup> 中调用 useStore()(或者使用 setup() 函数,像所有的组件那样) 之前,store 实例是不会被创建的:

<script setup>
import { useCounterStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 `store` 变量 
const store = useCounterStore()
</script>

与此同时,为了让 pinia 收益最大化,最好按照需求在不同的文件中定义 store。而且 store 一旦被实例化,你可以直接访问在 store 的 stategetters 和 actions 中定义的任何属性。

请注意,store 是一个用 reactive 包装的对象,这意味着不需要在 getters 后面写 .value。就像 setup 中的 props 一样,我们不能对它进行解构

<script setup>
import { useCounterStore } from '@/stores/counter'
import { computed } from 'vue'

const store = useCounterStore()
// 这将不起作用,因为它破坏了响应性
// 这就和直接解构 `props` 一样
const { name, doubleCount } = store
name // 将始终是 "Eduardo"
doubleCount // 将始终是 0
setTimeout(() => {
  store.increment()
}, 1000)
//  这样写是响应式的
//  当然你也可以直接使用 `store.doubleCount`
const doubleValue = computed(() => store.doubleCount)
</script>

从 Store 中解构

我们可以使用 storeToRefs() 方法,让 store 在保持响应性的同时从中提取属性。当只使用 store 的状态而不调用任何 action 时,这会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上:

<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { name, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>

总结

以上是对 Pinia 中 Store的简单介绍。在 Pinia 中,Store 是核心概念,Pinia正是通过这种方式来提供一个强大而灵活的状态管理模式,让 Vue 项目变得更加容易维护和拓展。