一直以来VueX都是官方为Vue提供的状态管理库,但是在Vue3的版本中,尽管VueX4也能提供组合式API,但是自身存在的很多缺陷让官方重新编写了一个新的库,也就是我们今天要学习的Pinia,结果发现这个新的状态库比起VueX要更加实用,它已经正式成为了Vue的官方推荐,而VueX仍然可以使用,但不会继续维护了,显而易见,Pinia才是未来的主流,所以必须学习它,它的设计思路和VueX非常类似,所以对于已经熟练掌握了VueX的开发者而言,学习Pinia非常轻松!
Pinia和VueX设计思想是一致的,但Pinia要简洁的多,它对于TypeScript的支持非常好,总结一下它们的区别:
Pinia对于Vue2和Vue3是都支持的,我们以Vue3为例来讲解它的使用:
通过命令在Vue3项目中安装Pinia,最新的版本是V2版本。
npm i pinia
然后在入口中使用Pinia
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const pinia = createPinia() // 调用方法,创建pinia实例,将pinia引入项目中
createApp(App).use(pinia).mount('#app')
和VueX类似,使用Pinia同样需要创建store,找到src目录,创建一个store。
通过defineStore方法创建一个store,这个方法接受2个参数,第一个参数是store的唯一id,第二个参数是store的核心配置,这里和VueX几乎一样,但是没有了mutations,而且注意state必须写成箭头函数的形式。defineStore方法返回的是一个函数,通过调用这个返回值函数可以得到具体的store实例。
// store/index.js 创建store(pinia创建的是小store
import { defineStore } from 'pinia'
// 接收函数/方法,用use开头
const useMainStore = defineStore('main', { // 起名,配置对象
state: () => {
return {
count: 0,
}
},
getters: {},
actions: {},
})
export { useMainStore }
在组件中可以引入store中导出的函数来得到store实例,从而访问其中的状态:
{{ mainStore.count }}
其中,mainStore.$state.count === mainStore.count为true
import useMainStore from './store'
const mainStore = useMainStore()
console.log(mainStore.$state.count === mainStore.count)
在pinia中,如果希望在Store中访问store实例的话,可以通过this
VueX是规定修改状态必须通过mutation实现,Pinia中移除了mutation概念,那应该如何访问和修改状态呢,一起来看看。
首先在state中初始化一些状态:
const useMainStore = defineStore('main', {
state: () => {
return {
count: 0,
name: '张三',
hobby: ['抽烟', '喝酒', '烫头'],
}
},
getters: {},
actions: {},
})
然后在组件中来访问和修改状态,如果希望通过解构的方式来访问状态里的数据,可以调用storeToRefs方法来保持数据的响应性。
{{ count }}
{{ name }}
{{ hobby }}
点击button按钮后,如果希望修改store中数据,vuex要求修改数据必须通过触发Mutation
pinia要求不那么严格,可以直接在组件中修改数据,但不推荐这么做:
{{ count }}
{{ name }}
{{ hobby }}
const changeStore = () => {
mainStore.$patch({
count: mainStore.count + 1,
name: '李四',
hobby: [...mainStore.hobby, '敲代码'],
})
}
mainStore.$patch((state) => { //state即是store中的状态
state.count++
state.name = '李四'
state.hobby.push('敲代码')
})
//store
actions: {
changeState(n) { //action中可以通过this访问到store实例
this.$patch((state) => {
state.count+=n
state.name = '李四'
state.hobby.push('敲代码')
})
},
},
//组件中
mainStore.changeState(10)
import { defineStore } from 'pinia'
const useMainStore = defineStore('main', {
actions: {
addCount(number) {
this.count += number
}
},
});
export default useMainStore
{{ mainStore.count10 }}
不能使用解构的写法来在响应式对象中简化掉mainStore.的内容,否则会导致响应性缺失
如果希望简化写法,需要用storeToRefs(mainStore)来包裹mainStore,即可简化不写mainStore.
{{ count10 }}
{{ name }}
{{ item }}
启动项目后可以看到基本的页面结构
准备完毕页面结构后,需要模拟一个接口请求,可以在src目录下新建一个api目录创建一个模拟的数据。
api/product.js
const products = [
{
id: 1,
title: 'iphone 13',
price: 5000,
inventory: 3,
},
{
id: 2,
title: 'xiaomi 12',
price: 3000,
inventory: 20,
},
{
id: 3,
title: 'macbook air',
price: 9900,
inventory: 8,
},
]
export const getProducts = async () => {
await wait(100)
return products
}
export const buyProducts = async () => {
await wait(100)
return Math.random() > 0.5
}
function wait(delay) {
return new Promise((res) => {
setTimeout(res, delay)
})
}
创建商品的store,在actions中定义初始化列表的逻辑。
//store/product.js
import { defineStore } from 'pinia'
import { getProducts } from '../api/product.js'
export const useProductStore = defineStore('product', {
state: () => {
return {
lists: [],
}
},
getters: {},
actions: {
async getProductLists() {
const productLists = await getProducts()
this.lists = productLists
},
},
})
在列表组件中触发action,然后渲染数据:
-
{{ item.title }}-{{ item.price }}
在浏览器中确认页面正确渲染
购物车组件需要创建专属的store,actions中也需要完成添加商品的逻辑,这里也是编码的重点,可以先把大体框架完成。
store/shopCart.js
import { defineStore } from 'pinia'
defineStore('shopCart', {
state: () => {
return {
lists: [],
}
},
getters: {},
actions: {
addProduct(product) {
//这里书写添加商品的逻辑
},
decrement(prodcut) { //减少库存数量
const res = this.lists.find((item) => item.id === prodcut.id)
res.inventory--
},
},
})
现在完成添加商品的逻辑,它应该包括这些要求:
然后用代码实现:
addProduct(product) {
//这里书写添加商品的逻辑
if (product.inventory < 1) {
return
}
const res = this.lists.find((item) => item.id === product.id)
if (res) {
res.number++
} else {
this.lists.push({
id: product.id,
title: product.title,
price: product.price,
number: 1,
})
}
const pStore = useProductStore()
pStore.decrement(product)
},
store中的逻辑完成后,在购物车和商品列表组件中渲染视图:
购物车
-
{{ item.title }}-{{ item.price }}
完成添加商品的核心逻辑后,再来完成总价计算和结算结果的显示。
总价使用getter计算得来:
store/shopCart.js
getters: {
sum(state) {
return state.lists.reduce((total, item) => {
return total + item.price * item.number
}, 0)
},
},
点击结算按钮,发送请求来获得结算结果,在购物车store中定义一个action来处理结算结果:
store/shopCart.js
state: () => {
return {
...
result: '',
}
},
actions: {
...
async getResult() {
const res = await buyProducts()
this.result = res ? '成功' : '失败'
if (res) { //结算成功后清空购物车列表
this.lists = []
}
},
},
所有购物车的基本功能都实现了,Pinia帮我们把所有的业务逻辑都提炼到了store中处理,大家有兴趣可以用VueX再来将这个案例完成一次,通过比较你会很快发现Pinia要比VueX更加方便。