Vue3 中使用 Vuex 和 Pinia 对比之 Pinia的用法

本文承接上一篇Vue3 中使用 Vuex 和 Pinia 对比之 Vuex的用法
与Vuex 相比,如果你觉得 Vuex 比较麻烦的话,那么Pinia提供了更简单的 API,具有更少的操作,简单理解一下,就是相当于把 Vuex 的 mutation 和 action 融成了一个,也就是说,比无需像 Vuex 要考虑异步任务,Pinia 让你直接异步请求操作修改数据状态。
Pinia(发音为 /piːnjʌ/,类似于英语中的“peenya”)是最接近有效包名 piña(西班牙语中的_pineapple_)的词。 菠萝实际上是一组单独的花朵,它们结合在一起形成多个水果。
Pinia 最初是为了探索 Vuex 的下一次迭代会是什么样子,结合了 Vuex 5 核心团队讨论中的许多想法。最终Pinia 团队意识到 Pinia 已经实现了在 Vuex 5 中想要的大部分内容,并决定实现它 取而代之的是新的建议。
与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的规范,提供了 Composition-API 风格的 API,最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持。

Pinia API 与 Vuex ≤4 有很大不同

  • 支持多Store:最大的不同是 Vuex 是单例的,只有一个 store ,而 Pinia 有多个 store;
  • Pinia中 mutations 不再存在。最初带来了 devtools 集成,但这不再是问题,Pinia 已被devtool 支持了。
  • store 的 action 被调度为常规的函数调用,而不是使用 dispatch 方法或 MapAction 辅助函数;
  • 无需创建自定义复杂包装器来支持 TypeScript,所有内容都是类型化的,并且 API 的设计方式尽可能利用 TS 类型推断。
  • 不再需要注入、导入函数、调用函数、享受自动完成功能!
  • 无需动态添加 Store,默认情况下它们都是动态的,您仍然可以随时手动使用 Store 进行注册,但因为它是自动的。
  • 不再有 modules 的嵌套结构。您仍然可以通过在另一个 Store 中导入和 使用 来隐式嵌套 Store,但 Pinia 通过设计提供平面结构,同时仍然支持 Store 之间的交叉组合方式。 您甚至可以拥有 Store 的循环依赖关系。
  • 没有 命名空间模块。鉴于 Store 的扁平架构,“命名空间” Store 是其定义方式所固有的,您可以说所有 Store 都是命名空间的。

Pinia 的核心概念

  • State - 等效于 Vuex 中的 State
  • Getters - 等效于 Vuex 中的 Getters
  • Actions - 等效于 Vuex 中的 Actions + mutations

Pinia 的使用

第一步:安装 Pinia

yarn add pinia
# 或者使用 npm
npm install pinia

第二步:定义一个 Store

import { defineStore } from 'pinia'

// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('test1', {
  // other options...
})

上面的 ‘test1’ ,这个 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

默认情况下,可以通过 store 实例访问状态来直接读取和写入状态:

const store = useStore()
store.counter++

重置状态值为初始值

可以通过调用 store 上的 $reset() 方法将状态 重置 到其初始值:

const store = useStore()
store.$reset()

访问 getter

getter 同 Vuex 一样,它也等效于 Vue 的 computed ,计算属性,和 state 一样直接调用即可

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    doubleCount: (state) => state.counter * 2,
  },
})

//在页面中使用
const store = useStore();
console.log(store.doubleCount);

getter 中是可以互相嵌套访问的,比如 getters 中有 getA 和 getB

getters:{
	getA:(state)=>state.counter *2,
	getB(){//需要使用 getA ,那么这个函数就不要用 箭头函数了,普通函数,通过 this 直接拿到 getA
		return this.getA +1;
	}
}

getter 传参

Getters 只是幕后的 computed 属性,因此无法向它们传递任何参数。 但是,您可以从 getter 返回一个函数以接受任何参数:

//定义 store
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>

Actions

Actions 相当于组件中的 methods。 它们可以使用 defineStore() 中的 actions 属性定义,并且它们非常适合定义业务逻辑:

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  actions: {
    increment() {
      this.counter++
    },
    randomizeCounter() {
      this.counter = Math.round(100 * Math.random())
    },
  },
})

//在组件中使用时,Actions 像 methods 一样被调用:
export default defineComponent({
  setup() {
    const main = useMainStore()
    // Actions 像 methods 一样被调用:
    main.randomizeCounter()

    return {}
  },
})

与 getters 一样,操作可以通过 this 访问 whole store instance 并提供完整类型(和自动完成)支持。 与它们不同,actions 可以是异步的,可以在其中await 任何 API 调用甚至其他操作!

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
      }
    },
  },
})

总结:Vuex 和 Pinia 我该怎么选?

个人觉得,你喜欢哪个用哪个,但是有如下对比,供大家自行参考:

名称 优点 缺点 适用(推荐)场景
Vuex 1. 更加成熟;
2. 也是因为第一点,所以它也更加稳定;
3. 更强大:提供了一些高级功能,比如:中间件和插件,使得它可以处理更加复杂的状态管理需求;
1. 学习成本高,相对于Pinia 难一些;
2. 相对来说比较繁琐,概念比较复杂;
3. 量级比较大,包体积也大 ;
1. 大型SPA项目;
2. 高复杂度且对 store 要求有更多功能和灵活性的开发者
Pinia 1. 轻量级,体积小 约 1KB;
2. 简单易用;
3. 更灵活,提供了多store ;
4. 允许捆绑器自动对它们进行代码拆分,并提供更好的 TypeScript 推理。
1. 相对来说有点新;
2. 生态不够完善,没有Vuex 那么庞大的社区支持和解决方案;
1. 中小型应用,需要简单轻量级的状态管理库的开发者;
2. 低复杂度的Vue 项目;
3. 更流畅的开发体验;

你可能感兴趣的:(前端,vue.js)