Vue的仓库pinia,实现各个组件之间的相互通信

475 阅读10分钟

上一次我们一起学习了一下父组件与子组件之间如何传递参数,这两个组件之间相互有关联。那两个不相干的组件之间应该如何传递参数呢?这就要用到Vue的仓库pinia了,简单来说它就像一个仓库,把各个组件需要用到的变量存到一个仓库里面。这样当某一个组件需要用到其它组件中的变量时,可以直接去仓库中找。

1. pinia

官方文档对它的定义是:

Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。如果你熟悉组合式 API 的话,你可能会认为可以通过一行简单的 export const state = reactive({}) 来共享一个全局状态。

那今天我们通过一个小demo来认识一下它。看看组件之间是如何进行通信的。

image.png

这是一个购物车的小demo,很简陋,样式也少。但无所谓,我们主要是用它来用一下pinia。我把上面的商品项单独写成了一个组件List.vue,下面的‘商品数量’、‘合计’和‘结算’也单独写成一个组件Car.vue。然后将它们作为两个组件引入到home页面中。

需求是当我们点击商品列表的’加入购物车‘按钮时,下面的商品数量需要加1,并且’合计‘中需要计算总价格。这是不是就涉及到了两个毫不相干之间的组件通信,当我们在List.vue组件中点击’加入购物车‘按钮时,Car.vue组件需要感知的到。

我们可以使用pinia来实现它,我们一起来看一下它是怎么使用的吧。

我们需要先安装一下pinia,输入一下指令:

npm install pinia

安装完了之后我们来使用它,我们来到src文件夹下创建一个store文件夹,我们在这里面来放我们的仓库。然后创建一个index.js文件。

我们在index.js文件中来创建一个pinia的实例对象。

import { createPinia } from 'pinia'

const store = createPinia()

export default store

先从pinia中引入createPinia函数,用它来创建一个实例对象store,然后全局抛出它。是不是和vue-router的用法如出一辙,因为是同一个作者写的嘛。

那就和vue-router一样,我们抛出了它,那就要使用它。因为最后我们所有的内容都要放到App.vue中展示,所以我们来到main.js中,让App使用它。

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
import store from './store/index.js'

createApp(App).use(router).use(store).mount('#app')

和router一样,use调用它。

我们在index.js中干的操作就是将仓库这个概念引入,就相当于我们的Vue从此有仓库这个东西了,融合在一起了。这样Vue碰到pinia的语法也能读懂它了。

接下来才来创建我们List.vue和Car.vue两个组件需要用到的仓库。还是在store文件夹下,就创建一个叫car.js的吧,用它来作为我们两个组件之间的仓库。

import { defineStore } from 'pinia' // 开创一个可用的仓库

export const useCarStore = defineStore('car', () => {
   
})

我们需要从pinia引入一个函数defineStore,定义一个仓库。我们const一个 useCarStore 为 defineStore 的执行结果。这个 useCarStore 我们可以自己取,但官方推荐use开头。defineStore函数的第一个参数要求是一个独一无二的名字,那我们就取‘car’,第二个参数就为一个回调函数。然后抛出这个仓库 useCarStore 。

那我们就开始往仓库中存我们要用的参数了。

在List.vue中,我使用了一个数组来存放商品列表项中需要用的数据,现在,我们可以把这个数组放到这个仓库中来。

import { defineStore } from 'pinia' // 开创一个可用的仓库

export const useCarStore = defineStore('car', () => {
   const listarr = [
        {
            pic: 'https://img0.baidu.com/it/u=3664559370,4167784460&fm=253&fmt=auto&app=138&f=JPEG?w=823&h=800',
            name: '无线蓝牙耳机',
            price: 100,
            desc: '高清音质 持久续航'
        },
        {
            pic: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcbu01.alicdn.com%2Fimg%2Fibank%2FO1CN01kT1RqH1xeqow7dlGT_%21%212208418306469-0-cib.jpg&refer=http%3A%2F%2Fcbu01.alicdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1737114845&t=62fa6ec91546fa59b6f5eb71665fce06',
            name: '智能手表',
            price: 599,
            desc: '多功能运动监测'
        },
        {
            pic: 'https://img2.baidu.com/it/u=2406623817,3374856095&fm=253&fmt=auto&app=120&f=JPEG?w=750&h=500',
            name: '便携音响',
            price: 399,
            desc: '环绕立体声'
        },
        {
            pic: 'https://img2.baidu.com/it/u=402051968,367416785&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
            name: '移动电源',
            price: 199,
            desc: '大容量快充'
        },
    ]
    
    return {
        listarr,
    }
})

直接在回调函数中声明一个数组listarr,里面放商品列表项需要用到的数据。最后一定要记得return这个数组。

仓库做好了之后,我们怎么在List.vue使用这个仓库呢?我们不是export抛出了它嘛。那我们直接在List.vue引入它就行了。

<template>
    <div>
        <ul>
            <li v-for="(item, index) in carStore.listarr" :key="index">
                <img :src="item.pic" alt="">
                <p>{{ item.name }}</p>
                <p>{{ item.desc }}</p>
                <div class="price">
                    <span>¥{{ item.price }}</span>
                    <button @click="addcar(item)">加入购物车</button>
                </div>
            </li>
        </ul>
    </div>
</template>

<script setup>
import { useCarStore } from '../store/car.js'

const carStore = useCarStore()

console.log(carStore);
</script>

<style lang="css" scoped>
ul {
    display: flex;
}

li {
    width: 224px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    margin: 20px;
    padding: 10px;
}

img {
    width: 100%;
    height: 224px;
}

.price {
    display: flex;
    justify-content: space-between;
}
</style>

我们import引入这个仓库useCarStore,这个useCarStore是一个函数,我们调用它得到一个carStore,我给你打印输出看一下这是一个什么东西。

PixPin_2024-12-19_10-27-47.png

我们得到的是一个对象,里面有一个listarr就是我们在仓库中添加的数组。那我们要使用它就carStore.listarr这样使用吧,我们就把它拿到li里去v-for遍历展示出来。

<template>
    <div>
        <ul>
            <li v-for="(item, index) in carStore.listarr" :key="index">
                <img :src="item.pic" alt="">
                <p>{{ item.name }}</p>
                <p>{{ item.desc }}</p>
                <div class="price">
                    <span>¥{{ item.price }}</span>
                    <button @click="addcar(item)">加入购物车</button>
                </div>
            </li>
        </ul>
    </div>
</template>

这样我们就成功使用了仓库中的数据展示了。

image.png

接下来我们要实现Car.vue中的数据展示了,Car.vue中需要一个‘商品数量’和‘合计’,那我们就在仓库中声明一个变量 selectNum 代表‘商品数量’,totalPrice 代表‘合计’,初始值为0。

import { defineStore } from 'pinia' // 开创一个可用的仓库

export const useCarStore = defineStore('car', () => {
   const listarr = [
        {
            pic: 'https://img0.baidu.com/it/u=3664559370,4167784460&fm=253&fmt=auto&app=138&f=JPEG?w=823&h=800',
            name: '无线蓝牙耳机',
            price: 100,
            desc: '高清音质 持久续航'
        },
        {
            pic: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcbu01.alicdn.com%2Fimg%2Fibank%2FO1CN01kT1RqH1xeqow7dlGT_%21%212208418306469-0-cib.jpg&refer=http%3A%2F%2Fcbu01.alicdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1737114845&t=62fa6ec91546fa59b6f5eb71665fce06',
            name: '智能手表',
            price: 599,
            desc: '多功能运动监测'
        },
        {
            pic: 'https://img2.baidu.com/it/u=2406623817,3374856095&fm=253&fmt=auto&app=120&f=JPEG?w=750&h=500',
            name: '便携音响',
            price: 399,
            desc: '环绕立体声'
        },
        {
            pic: 'https://img2.baidu.com/it/u=402051968,367416785&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
            name: '移动电源',
            price: 199,
            desc: '大容量快充'
        },
    ]
    
    const selectNum = 0
    const totalPrice = 0
    
    return {
        listarr,
        selectNum,
        totalPrice,
    }
})

最后一定要记得返回它们。这样我们又在仓库中添加了两个变量。那我们想在Car.vue中使用它们,不就和List.vue中的操作一样嘛,先引入这个仓库。

<template>
    <div class="car">
        <div class="total">
            <p>商品数量:{{ carStore.selectNum }}</p>
            <p>合计:{{ carStore.totalPrice }}</p>
        </div>
        <div class="pay">结算</div>
    </div>
</template>

<script setup>
import { useCarStore } from '../store/car.js'
const carStore = useCarStore()

</script>

<style lang="css" scoped>
.car {
    padding: 30px;
    width: 1000px;
    display: flex;
    justify-content: space-between;
}
</style>

然后调用它,我们也取一个carStore,这样carStore就是一个对象,里面存放着我们要用的数据。我们直接拿到页面中展示。

image.png

成功展示了。

好,我们成功的在仓库中定义了参数并且把仓库中的数据拿到需要使用它们的组件中去使用了。以上我们干的都是拿仓库中的数据出来展示。组件通信之间还有很重要的一点是要更改数据。

我们的需求还没有完成,当我们点击‘加入购物车’按钮时,商品数量需要加1,并且合计中要计算总价。这就涉及到了更改仓库中的数据了。

我们在更改仓库中的数据会保持这样一个原则:仓库中的数据修改,应该使用仓库中的方法进行修改,而不要将数据引入到组件中后直接修改

所以我们应该在仓库中就定义好更改仓库中数据的方法。

我们可以在仓库中定义一个数组,selectArr,用来保存被选中的商品。当用户点击某一个商品的‘加入购物车’按钮时,就将当前商品在listarr数组中对应的对象保存在selectArr中,这样我们就能获取到用户加入购物车的是哪件商品并且能知道它的单价,方便我们后面计算总价。

import { defineStore } from 'pinia' // 开创一个可用的仓库
import { computed, reactive } from 'vue'

export const useCarStore = defineStore('car', () => {
   const listarr = [
        {
            pic: 'https://img0.baidu.com/it/u=3664559370,4167784460&fm=253&fmt=auto&app=138&f=JPEG?w=823&h=800',
            name: '无线蓝牙耳机',
            price: 100,
            desc: '高清音质 持久续航'
        },
        {
            pic: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcbu01.alicdn.com%2Fimg%2Fibank%2FO1CN01kT1RqH1xeqow7dlGT_%21%212208418306469-0-cib.jpg&refer=http%3A%2F%2Fcbu01.alicdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1737114845&t=62fa6ec91546fa59b6f5eb71665fce06',
            name: '智能手表',
            price: 599,
            desc: '多功能运动监测'
        },
        {
            pic: 'https://img2.baidu.com/it/u=2406623817,3374856095&fm=253&fmt=auto&app=120&f=JPEG?w=750&h=500',
            name: '便携音响',
            price: 399,
            desc: '环绕立体声'
        },
        {
            pic: 'https://img2.baidu.com/it/u=402051968,367416785&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
            name: '移动电源',
            price: 199,
            desc: '大容量快充'
        },
    ]
    
    const selectNum = 0
    const totalPrice = 0
    
    const selectArr = reactive([]) // 选中的商品
    
    const addselectArr = () => {
        
    }
    
    return {
        listarr,
        selectNum,
        totalPrice,
        addselectArr
    }
})

我们定义一个函数addselectArr,它的作用就是将用户选中的商品push到selectArr数组中保存。那addselectArr怎么获取到用户选中的商品呢?

我们在仓库中return了这个函数addselectArr,它就可以被拿到List.vue中去使用了。

List.vue:

<template>
    <div>
        <ul>
            <li v-for="(item, index) in carStore.listarr" :key="index">
                <img :src="item.pic" alt="">
                <p>{{ item.name }}</p>
                <p>{{ item.desc }}</p>
                <div class="price">
                    <span>¥{{ item.price }}</span>
                    <button @click="addcar(item)">加入购物车</button>
                </div>
            </li>
        </ul>
    </div>
</template>

<script setup>
import { useCarStore } from '../store/car.js'

const carStore = useCarStore()

//console.log(carStore);

const addcar = (item) => {
    carStore.addselectArr(item)
}

</script>

<style lang="css" scoped>
ul {
    display: flex;
}

li {
    width: 224px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    margin: 20px;
    padding: 10px;
}

img {
    width: 100%;
    height: 224px;
}

.price {
    display: flex;
    justify-content: space-between;
}
</style>

我们要给‘加入购物车’按钮绑定一个点击事件,就叫addcar(item),这里就可以获取到用户选中的商品item,然后我们在addcar事件中将这个item传给addselectArr就行了。我们要使用addselectArr函数是不是也得carStore.addselectArr()去调用啊。这样addselectArr函数就获取到了用户选中的商品,我们再将item添加到selectArr数组中去。

回到仓库car.js中:

import { defineStore } from 'pinia' // 开创一个可用的仓库
import { computed, reactive } from 'vue'

export const useCarStore = defineStore('car', () => {
   const listarr = [
        {
            pic: 'https://img0.baidu.com/it/u=3664559370,4167784460&fm=253&fmt=auto&app=138&f=JPEG?w=823&h=800',
            name: '无线蓝牙耳机',
            price: 100,
            desc: '高清音质 持久续航'
        },
        {
            pic: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcbu01.alicdn.com%2Fimg%2Fibank%2FO1CN01kT1RqH1xeqow7dlGT_%21%212208418306469-0-cib.jpg&refer=http%3A%2F%2Fcbu01.alicdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1737114845&t=62fa6ec91546fa59b6f5eb71665fce06',
            name: '智能手表',
            price: 599,
            desc: '多功能运动监测'
        },
        {
            pic: 'https://img2.baidu.com/it/u=2406623817,3374856095&fm=253&fmt=auto&app=120&f=JPEG?w=750&h=500',
            name: '便携音响',
            price: 399,
            desc: '环绕立体声'
        },
        {
            pic: 'https://img2.baidu.com/it/u=402051968,367416785&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
            name: '移动电源',
            price: 199,
            desc: '大容量快充'
        },
    ]
    
    const selectNum = 0
    const totalPrice = 0
    
    const selectArr = reactive([]) // 选中的商品
    
    const addselectArr = (item) => {
        selectArr.push(item)
    }
    
    return {
        listarr,
        selectNum,
        totalPrice,
        addselectArr
    }
})

我们就可以在addselectArr函数中将item添加到selectArr数组中去。这样当用户每点击一次添加按钮,List.vue中的addcar函数就会触发一次,addcar函数中的addselectArr函数就会触发一次,就会将item添加到selectArr数组中去。注意这里我们将selectArr数组声明为了响应式,所以要引入reactive。

这样我们就获取到了用户选中的商品。这样,每次selectArr数组添加一项,就让selectNum读一下它的长度,不就能获取到商品数量了嘛。

那selectNum的值需要跟着selectArr.length的值改变而改变。是不是要用计算属性了呀。

我们可以使用computed。

import { defineStore } from 'pinia' // 开创一个可用的仓库
import { computed, reactive } from 'vue'

export const useCarStore = defineStore('car', () => {
   const listarr = [
        {
            pic: 'https://img0.baidu.com/it/u=3664559370,4167784460&fm=253&fmt=auto&app=138&f=JPEG?w=823&h=800',
            name: '无线蓝牙耳机',
            price: 100,
            desc: '高清音质 持久续航'
        },
        {
            pic: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcbu01.alicdn.com%2Fimg%2Fibank%2FO1CN01kT1RqH1xeqow7dlGT_%21%212208418306469-0-cib.jpg&refer=http%3A%2F%2Fcbu01.alicdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1737114845&t=62fa6ec91546fa59b6f5eb71665fce06',
            name: '智能手表',
            price: 599,
            desc: '多功能运动监测'
        },
        {
            pic: 'https://img2.baidu.com/it/u=2406623817,3374856095&fm=253&fmt=auto&app=120&f=JPEG?w=750&h=500',
            name: '便携音响',
            price: 399,
            desc: '环绕立体声'
        },
        {
            pic: 'https://img2.baidu.com/it/u=402051968,367416785&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
            name: '移动电源',
            price: 199,
            desc: '大容量快充'
        },
    ]
    
    const selectNum = computed(() => {
        return selectArr.length
    })
    
    const totalPrice = 0
    
    const selectArr = reactive([]) // 选中的商品
    
    const addselectArr = (item) => {
        selectArr.push(item)
    }
    
    return {
        listarr,
        selectNum,
        totalPrice,
        addselectArr
    }
})

这样只要selectArr数组中添加一项,selectNum就会重新赋值。

我们来看一下效果:

PixPin_2024-12-19_12-03-26.gif

成功的实现了这个效果。那totalPrice是不是就是一样的操作啊。

import { defineStore } from 'pinia' // 开创一个可用的仓库
import { computed, reactive } from 'vue'

export const useCarStore = defineStore('car', () => {
   const listarr = [
        {
            pic: 'https://img0.baidu.com/it/u=3664559370,4167784460&fm=253&fmt=auto&app=138&f=JPEG?w=823&h=800',
            name: '无线蓝牙耳机',
            price: 100,
            desc: '高清音质 持久续航'
        },
        {
            pic: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcbu01.alicdn.com%2Fimg%2Fibank%2FO1CN01kT1RqH1xeqow7dlGT_%21%212208418306469-0-cib.jpg&refer=http%3A%2F%2Fcbu01.alicdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1737114845&t=62fa6ec91546fa59b6f5eb71665fce06',
            name: '智能手表',
            price: 599,
            desc: '多功能运动监测'
        },
        {
            pic: 'https://img2.baidu.com/it/u=2406623817,3374856095&fm=253&fmt=auto&app=120&f=JPEG?w=750&h=500',
            name: '便携音响',
            price: 399,
            desc: '环绕立体声'
        },
        {
            pic: 'https://img2.baidu.com/it/u=402051968,367416785&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
            name: '移动电源',
            price: 199,
            desc: '大容量快充'
        },
    ]
    
    const selectNum = computed(() => {
        return selectArr.length
    })
    
    const totalPrice = computed(() => {
        let totalPrice = 0
        for (let i = 0; i < selectArr.length; i++) {
            totalPrice += selectArr[i].price
        }
        return totalPrice
    })
    
    const selectArr = reactive([]) // 选中的商品
    
    const addselectArr = (item) => {
        selectArr.push(item)
    }
    
    return {
        listarr,
        selectNum,
        totalPrice,
        addselectArr
    }
})

selectArr数组中每添加一项,我们就去遍历数组每一项,将price加起来,这样就实现了总价的计算。

PixPin_2024-12-19_12-06-35.gif

这样我们就完成了这个小demo。

2. 总结

本篇文章我们一起学习了一下pinia,它就好像一个vue的仓库一样,可以将各个组件共用的一些变量存放到这个仓库中,这样当两个组件各自引入同一个仓库对象,就能实现对仓库中的变量进行访问和修改。

我们通过一个小demo来认识了一下pinia的语法,应该对组件之间的通信有了一个更加深刻的认识。如果对你有帮助的话不妨点个赞吧。