本文承接上一篇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. 更流畅的开发体验; |