Pinia的使用

80 阅读5分钟

image.png 官网地址: Pinia 🍍

前言

最近完全使用Vue3的技术框架进行业务开发了,在Vue3的体系下,一般用Pinia来代替Vuex来作为统一状态管理。趁着这个机会,总结一波Pinia的使用。 Store (如 Pinia) 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,它承载着全局状态。它有点像一个永远存在的组件,每个组件都可以读取和写入它。它有三个概念stategetteraction,我们可以假设这些概念相当于组件中的 datacomputed methods

优点

就我自己使用的感受来说,

  1. 比之前Vuex使用起来更方便,约束没有那么多,也不再区分mutationaction
  2. 模块化定义,可以定义多个store,使用起来就和平常模块一样
  3. store里面还可以引用另外store里面的stategetteraction,非常方便
  4. storeaction里面,可以直接调用异步方法

安装&配置

如果是通过Vue的脚手架新建项目,可以直接选择Pinia,不需要单独安装。

# 通过npm包进行安装
npm install pinia
// main.js
import { createApp } from 'vue'
// 引入pinia
import { createPinia } from 'pinia'
import App from './App.vue'

// 创建实例
const pinia = createPinia()
const app = createApp(App)

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

使用

定义和使用Store

建议可以在src文件夹下新建一个store文件夹,专门放置各种store。像在优点中介绍的,Pinia使用模块化设计,首先需要定义一个Store来管理状态。

// @/stores/counter.js
import { defineStore } from 'pinia'

// 第一个参数是你的应用中 Store 的唯一 ID。
export const useCounterStore = defineStore('counter', {
  // 其他配置...
  state: () => {
    return {
      count: 0,
    }
  },
  getters: {},
  actions: {}
})

使用defineStore来定义,defineStore接受两个参数,第一个参数是应用中Store的唯一ID,第二个参数里面配置stategettersactions

👆一般约定使用use开头,以Store结尾来命名defineStore的返回值。像上面代码中的 useCounterStore

定义好了的Store直接在需要使用的地方引入使用就行,不需要像Vuex之前Module一样声明。

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

定义和使用State

State可以类比做Vue2里面的响应式数据data,是Store里面管理的全局状态。定义state非常简单,就像以前声明data数据是一样的。

export const useCounterStore = defineStore('counter', {
  // 在这里定义state,state是个函数,需要返回一个对象
  state: () => {
    return {
      count: 0,
      items: [],
      hasChanged: true,
    }
  }
})

使用的话直接引入Store后,可以直接使用和赋值。不需要再通过提交mutation来改变状态的值。

const counterStore = useCounterStore()

// 直接赋值
counterStore.count++
// 也可以批量更新
counterStore.$patch({
  count: store.count + 1,
  hasChanged: false,
})

✌️上述两种方式都会触发响应式更新。Store里面的state和使用reactive进行响应式包装一样,可以直接在template中使用counterStore.count,也可以直接在script中counterStore.count操作,不需要counterStore.count.value

定义和使用getter

getter可以类比Vue2里面的computed,它和computed一样会自动计算里面使用到的依赖,当依赖发生更新时自动更新。

import { useOtherStore } from './other-store'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  getters: {
    // 推荐使用箭头函数,并且它将接收 state 作为第一个参数
    doubleCount: (state) => state.count * 2,
    // 也可以使用普通函数,普通函数内可以使用this访问整个state和getter
    doublePlusOne() {
      return this.doubleCount + 1
    },
    // 还可以使用其它Store里面的state和getter,并且也是响应式的
    // 这就非常方便
    otherGetter(state) {
      const otherStore = useOtherStore()
      return state.count + otherStore.data
    },
  },
})

Vue组件里面使用getter就和state一样,也是被当做属性使用,这里不再赘述。

解构State和Getter

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

<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'
const store = useCounterStore()

// ❌ 这将不起作用,因为它破坏了响应性
// 这就和直接解构 `props` 一样
const { name, doubleCount } = store
name // 将始终是 "Eduardo"
doubleCount // 将始终是 0
setTimeout(() => {
  store.increment()
}, 1000)

// ✅ 这样写是响应式的
// 这样解构之后,可以当做当个的ref使用
const { count, doubleCount } = storeToRefs(store)

// action的话可以直接解构
const { increment } = store
</script>

定义和使用action

action可以类比Vue2里面的method,通过this访问整个store实例,并且支持异步。

import { useOtherStore } from './other-store'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  getters: {
    // 推荐使用箭头函数,并且它将接收 state 作为第一个参数
    doubleCount: (state) => state.count * 2,
    // 也可以使用普通函数,普通函数内可以使用this访问整个state和getter
    doublePlusOne() {
      return this.doubleCount + 1
    },
    // 还可以使用其它Store里面的state和getter,并且也是响应式的
    // 这就非常方便
    otherGetter(state) {
      const otherStore = useOtherStore()
      return state.count + otherStore.data
    },
  },
  actions: {
    // 类似 getter,action 也可通过 this 访问整个 store 实例
    increment() {
      this.count++
    },
    // 并且是可以异步的
    // 你也完全可以自由地设置任何你想要的参数以及返回任何结果
    // 在这里也可以访问其它Store里面的action
    asyncIncrement() {
      try {
        this.count = await api.post({ data })
      } catch (error) {
        return error
      }
    },
  },
})

结尾

整个Pinia还支持ts类型的自动推断,支持Vue3setup语法,这部分内容可以自行查询 官网 Pinia 🍍。 在第一次开发过程中,一直感叹这个Store太好用了,还可以用来封装一个模块的业务逻辑。 但是使用Vue3hooks也是可以做到的,那什么时候用Store,什么时候用hooks呢? 我认为,当你这个状态全局多个组件会用到,并且是使用同一份状态的基础下,可以使用Store。如果你只是想封装一段业务逻辑,在不同组件里面复用,这个时候使用hooks

以上仅是个人学习过程中的一点总结,如有错误,敬请指正。