Vue3中的常见组件通信之`pinia`

Vue3中的常见组件通信之pinia

概述

​ 在vue3中常见的组件通信有props、mitt、v-model、 r e f s 、 refs、 refs、parent、provide、inject、pinia、slot等。不同的组件关系用不同的传递方式。常见的撘配形式如下表所示。

组件关系 传递方式
父传子 1. props 2. v-model 3. $refs 4. 默认插槽、具名插槽
子传父 1. props 2. 自定义事件 3. v-model 4. $parent 5. 作用域插槽
祖传孙、孙传祖 1. $attrs 2. provide、inject
兄弟间、任意组件间 1. mitt 2. pinia

props和自定义事件详见:
Vue3中的常见组件通信之props和自定义事件

mitt用法详见:
Vue3中的常见组件通信之mitt

v-model用法详见:
Vue3中的常见组件通信之v-model

$attrs用法详见:
Vue3中的常见组件通信之$attrs

$refs$parent详见:
Vue3中的常见组件通信之$refs$parent

provide和inject详见:
Vue3中的常见组件通信之provide和inject

接下来是pinia。

8.pinia

pinia是一个集中式状态管理工具,是符合直觉的集中式状态管理工具。

8.1准备组件

首先准备3个组件,父组件代码如下:

vue 复制代码
<template>
    <div class="father">
        <Header/>
        <Content/>
    </div>
</template>

<script setup lang="ts" name="Index">
import Header from './Header.vue';
import Content from './Content.vue';

</script>

<style scoped>
    .father{
        height: 300px;
        width: 800px;
        background-color: rgb(169, 169, 169);
        margin: 5px;
        padding: 0;
    }
</style>

Header组件代码如下:

vue 复制代码
<template>
    <div class="header">
        <img class="thoto" :src="user.thoto" alt="头像">
        <span>{{ user.name }}</span>
    </div>
</template>

<script setup lang="ts" name="Header">
import {ref,reactive} from 'vue'

let user = reactive({
    id:'m0_63165331',
    name:'m0_63165331',
    thoto:'https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406111305778.jpg'
})
</script>

<style scoped>
    .header{
        height: 50px;
        background-color: #3b818c;
        border-bottom: 1px solid rgb(255, 255, 255);
        overflow: hidden;
        font-size: 0;
        text-align: center;
        line-height: 50px;
    }
    span{
        font-size: 16px;
        vertical-align: middle;
        color: #fff;
    }
    .thoto{
        height: 35px;
        border-radius: 50%;
        vertical-align: middle;
        margin-right: 5px;
    }
</style>

Content组件代码如下:

vue 复制代码
<template>
    <div class="outer">
        <div class="user">        
            <img class="thoto" :src="user.thoto" alt="头像">  
            <br>
            <div class="user-inf">
                <span>账号:{{ user.id }}</span> 
                <br>
                <span>昵称:{{ user.name }}</span>
                <br>
                <span>性别:{{ user.gender }}</span>
                <br>
                <span>排名:{{ user.rank }}</span> 
                <br>
                <span>粉丝:{{ user.fans }}</span>  
            </div>      
        </div>
        <div class="content">
            <textarea name="msg" >评论内容</textarea>
        </div>
    </div>
</template>

<script setup lang="ts" name="Content">
import {ref,reactive} from 'vue'

let user = reactive({
    id:'m0_63165331',
    name:'m0_63165331',
    gender:'男',
    rank:19102,
    fans:1040,
    thoto:'https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406111305778.jpg'
})
</script>

<style scoped>
    .outer{
        height: 250px;
        overflow: hidden;
    }
    .user{
        height: 240px;
        width: 150px;
        margin: 5px;
        background-color: #c6e6e8;
        text-align: center;
        float: left;
        border-radius: 5px;
        box-shadow: 0 0 5px black;
    }

    .user-inf{
        width: 150px;
        text-align: left;
        padding: 0 20px;
    }

    span{
        font-size: 16px;
        color: #000;
        font-size: 10px;
        font-family: 微软雅黑;
        font-weight: 600;        
    }

    .thoto{
        height: 50px;
        border-radius: 40%;
        margin: 10px;
    }

    .content{
        width:630px;
        height: 240px;
        /* background-color: #c7d2d4; */
        float: right;
        margin: 5px;        
    }
    textarea{
        width:630px;
        height: 240px;
        background-color: #c6e6e8;
        border: none;
        padding: 5px;
        border-radius: 5px;
        box-shadow: 0 0 5px black;
    }
</style>

呈现效果如下:

8.2 搭建pinia环境

第一步先安装pinia

sh 复制代码
npm indtall pinia

第二步在src/main.ts中引入createPinia并创建pinia,然后再安装pinia插件,如下代码:

typescript 复制代码
import {createApp} from 'vue'
import App from './App.vue'
//1.引入createPinia,用于创建pinia
import {createPinia} from 'pinia'

// 创建应用
const app = createApp(App)

// 2.创建pinia
const pinia = createPinia()

// 3.安装pinia插件
app.use(pinia)

// 挂载应用
app.mount('#app')

此时我们已经创建了pinia环境,在浏览器开发者工具中能看到pinia

8.3存储和读取数据

pinia是一个轻量化的状态管理工具,一般把组件中共用的数据存储在store中,不适合把所有的数据都用pinia来管理。前面创建的例子中user数据是共用的,可以放入到store中。

Store是一个保存:状态业务逻辑 的实体,每个组件都可以读取写入它。

它有三个概念:stategetteraction,相当于组件中的: datacomputedmethods

在src文件夹下新建stoer文件夹,然后在文件夹下新建文件user.ts。这个文件用于存储user相关的数据、逻辑。

在user.ts文件中先//引入defineStore,用于创建store。

typescript 复制代码
//引入defineStore,用于创建store
import {defineStore} from 'pinia'

store的变量名建议以use开头Srore结尾的大驼峰式命名。如下代码创建useUserStore,并暴露。defineStore接收两个参数,第一个参数是id值,官方建议与文件名保持一致,第二个参数有两种写法,第一种是选项式写法,参数是配置对象,第二种是组合式写法,是函数。

如下代码是选项式写法:它有一个state配置项,state必须写成函数,内部return一个对象,对象内部写具体的数据,如下:

typescript 复制代码
export const useUserStore = defineStore('user',{
    //状态
    state() {
        return {
            user:{
                id:'m0_63165331',
                name:'m0_63165331',
                gender:'男',
                rank:19102,
                fans:1040,
                thoto:'https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406111305778.jpg'
            }
        }
    },
})

这样写完就拥有了一个用于存储user相关内容的仓库,接下来需要在组件中引入仓库,引入后可直接调用便可得到对应的store,如下代码:

typescript 复制代码
// 引入对应的useXxxxxStore	
import { useUserStore } from '../store/user';
// 调用useXxxxxStore得到对应的store
const userStore = useUserStore()

那个这个userStore具体是什么,可以在控制台打印输出得到如下内容:

从上面控制台打印的信息可以得到,userStore是个响应式对象,它身上有user对象,还有 s t a t e , state, state,state身上也有user对象,说明有两种方式可以拿到user对象的数据,如下:

typescript 复制代码
console.log(userStore.user)
console.log(userStore.$state.user)

控制台结果如下:

可以看到两种拿数据的方法得到的结果是完全一样的,所以我们使用简单的方式来拿数据,在页面中展示用如下代码:

vue 复制代码
<div class="user">        
    <img class="thoto" :src="userStore.user.thoto" alt="头像">  
    <br>
    <div class="user-inf">
        <span>账号:{{ userStore.user.id }}</span> 
        <br>
        <span>昵称:{{ userStore.user.name }}</span>
        <br>
        <span>性别:{{ userStore.user.gender }}</span>
        <br>
        <span>排名:{{ userStore.user.rank }}</span> 
        <br>
        <span>粉丝:{{ userStore.user.fans }}</span>  
    </div>      
</div>

运行后结果跟没有用pinia之前是完全一样的,不再附图。

8.4 修改数据

pinia修改数据有三种方式。

**第一种修改方式:**是符合直觉的方式,拿到数据后就直接修改,如下代码,先创建一个方法用来更改粉丝数量:

typescript 复制代码
function addFans(){
    userStore.user.fans += 1
}

然后在组件中添加按钮并绑定事件:

vue 复制代码
<button @click="addFans">粉丝数量+1</button>

运行后效果如下:

**第二种修改方式:**是批量修改方式,用$path,它只需要把要修改的数据传递即可,如下代码:

typescript 复制代码
function changeInf(){
    userStore.$patch( {
        user : {
            name:'一叶知秋',
            rank:5000,
            fans:10000,
        }
    })
}

添加按钮并绑定事件,如下:

vue 复制代码
<button @click="changeInf">更改数据</button>

运行效果如下:

**第三种修改方式:**借助pinia的action修改。在user.ts文件中的defineStore第二个参数中增加如下配置项:

typescript 复制代码
//动作
actions:{
    changeRankAddFans(){
        if (this.user.fans<10000){
            this.user.fans += 1000
        };
        if (this.user.rank>10000){
            this.user.rank -= 1000
        }
    }
},

在组件中引入过userStore后可以直接调用,如下代码添加按钮并绑定事件:

vue 复制代码
<button @click="userStore.changeRankAddFans">粉丝数量+1000,排名往前1000</button>

运行后如下效果:

8.5 getters

pinia中的getters类似计算属性,如下代码所示,在user.ts中添加getters配置项:

typescript 复制代码
getters:{
    changeRank(state){
        if (state.user.rank > 10000){
            return Math.floor(state.user.rank / 10000) + "万+"
        }else{
            return state.user.rank
        }
    }
}

在组件页面中调整显示内容如下:

html 复制代码
<span>排名:{{ userStore.changeRank }}</span> 

运行后效果如下,排名超过1万时显示1万+,在1万名以内显示具体数字:

8.6 $subscribe

pinia中的$subscribe可以对pinia仓库中的数据进行监视,与watch类似,如下代码:

typescript 复制代码
userStore.$subscribe(()=>{
    console.log('userStore中的数据发生变化了')
})

$subscribe是一个函数,需要调用,调用的同时要传入一个函数,可以用箭头函数。上面代码运行后,当数据发生变化的时候这句话就会打印,如下图示意:

$subscribe函数传入的函数中可以接收两个参数,第一个参数是数据变化的信息,第二个参数是变化后的数据。如下代码示意:

typescript 复制代码
userStore.$subscribe((mutate,state)=>{
    console.log('userStore中的数据发生变化了')
    console.log(mutate)
    console.log(state)
})

8.7 store组合式写法

前面8.3小节提到过store有两种写法,一种是选项式,一种是组合式,前面演示的均为选项式,下面代码为组合式写法。

typescript 复制代码
export const useUserStore = defineStore('user',()=>{
    let user = reactive({
        id:'m0_63165331',
        name:'m0_63165331',
        gender:'男',
        rank:19102,
        fans:1040,
        thoto:'https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406111305778.jpg'
    })
    
    function changeRankAddFans(){
        if (user.fans<10000){
            user.fans += 1000
        };
        if (user.rank>10000){
            user.rank -= 1000
        }
    }
    
    let changeRank = computed(()=>{
        if (user.rank > 10000){
            return Math.floor(user.rank / 10000) + "万+"
        }else{
            return user.rank
        }
    })

    return {user,changeRankAddFans,changeRank}
})

8.8 小结

pinia是符合直接的vue.js的状态管理工具。

Store是一个保存:状态业务逻辑 的实体,每个组件都可以读取写入它。

它有三个概念:stategetteraction,相当于组件中的: datacomputedmethods

它可以按照选项式风格来编写代码,也可以按照组合式风格来编写代码。

以下是完整代码:

src/main.ts中代码如下

typescript 复制代码
import {createApp} from 'vue'
import App from './App.vue'

//引入createPinia,用于创建pinia
import {createPinia} from 'pinia'

// 创建应用
const app = createApp(App)

// 创建pinia
const pinia = createPinia()

// 安装pinia插件
app.use(pinia)

// 挂载应用
app.mount('#app')

src/store/user.ts中代码如下:

typescript 复制代码
//引入defineStore,用于创建store
import {defineStore} from 'pinia'
import {reactive,computed} from 'vue'

//创建seUserStore并暴露
//选项式写法
// export const useUserStore = defineStore('user',{
//     //状态
//     state() {
//         return {
//             user:{
//                 id:'m0_63165331',
//                 name:'m0_63165331',
//                 gender:'男',
//                 rank:19102,
//                 fans:1040,
//                 thoto:'https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406111305778.jpg'
//             }
//         }
//     },
//     //动作
//     actions:{
//         changeRankAddFans(){
//             if (this.user.fans<10000){
//                 this.user.fans += 1000
//             };
//             if (this.user.rank>10000){
//                 this.user.rank -= 1000
//             }
//         }
//     },
//     //计算
//     getters:{
//         changeRank(state){
//             if (state.user.rank > 10000){
//                 return Math.floor(state.user.rank / 10000) + "万+"
//             }else{
//                 return state.user.rank
//             }
//         }
//     }

// })

//组合式写法
export const useUserStore = defineStore('user',()=>{
    let user = reactive({
        id:'m0_63165331',
        name:'m0_63165331',
        gender:'男',
        rank:19102,
        fans:1040,
        thoto:'https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406111305778.jpg'
    })
    
    function changeRankAddFans(){
        if (user.fans<10000){
            user.fans += 1000
        };
        if (user.rank>10000){
            user.rank -= 1000
        }
    }
    
    let changeRank = computed(()=>{
        if (user.rank > 10000){
            return Math.floor(user.rank / 10000) + "万+"
        }else{
            return user.rank
        }
    })

    return {user,changeRankAddFans,changeRank}
})

Father组件中代码:

vue 复制代码
<template>
    <div class="father">
        <Header/>
        <Content/>
    </div>
</template>

<script setup lang="ts" name="Index">
import Header from './Header.vue';
import Content from './Content.vue';

</script>

<style scoped>
    .father{
        height: 300px;
        width: 800px;
        background-color: rgb(169, 169, 169);
        margin: 5px;
        padding: 0;
    }
</style>

Header组件代码:

vue 复制代码
<template>
    <div class="header">
        <img class="thoto" :src="userStore.user.thoto" alt="头像">
        <span>{{ userStore.user.name }}</span>
    </div>
</template>

<script setup lang="ts" name="Header">
// 引入对应的useXxxxxStore	
import { useUserStore } from '../store/user';
// 调用useXxxxxStore得到对应的store
const userStore = useUserStore()


</script>

<style scoped>
    .header{
        height: 50px;
        background-color: #3b818c;
        border-bottom: 1px solid rgb(255, 255, 255);
        overflow: hidden;
        font-size: 0;
        text-align: center;
        line-height: 50px;
    }
    span{
        font-size: 16px;
        vertical-align: middle;
        color: #fff;
    }
    .thoto{
        height: 35px;
        border-radius: 50%;
        vertical-align: middle;
        margin-right: 5px;
    }
</style>

Content组件:

vue 复制代码
<template>
    <div class="outer">
        <div class="user">        
            <img class="thoto" :src="userStore.user.thoto" alt="头像">  
            <br>
            <div class="user-inf">
                <span>账号:{{ userStore.user.id }}</span> 
                <br>
                <span>昵称:{{ userStore.user.name }}</span>
                <br>
                <span>性别:{{ userStore.user.gender }}</span>
                <br>
                <span>排名:{{ userStore.changeRank }}</span> 
                <br>
                <span>粉丝:{{ userStore.user.fans }}</span>  
            </div>      
        </div>
        <div class="content">
            <textarea name="msg" >评论内容</textarea>
            <button @click="addFans">粉丝数量+1</button>
            <button @click="changeInf">更改数据</button>
            <button @click="userStore.changeRankAddFans">粉丝数量+1000,排名往前1000</button>
        </div>
    </div>
</template>

<script setup lang="ts" name="Content">
import {ref,reactive,toRefs} from 'vue'
// 引入对应的useXxxxxStore	
import { useUserStore } from '../../store/user';
// 调用useXxxxxStore得到对应的store
const userStore = useUserStore()

userStore.$subscribe((mutate,state)=>{
    console.log('userStore中的数据发生变化了')
    console.log(mutate)
    console.log(state)
})

//修改数据第一种方式
function addFans(){
    userStore.user.fans += 1
}

//修改数据第二种方式
function changeInf(){
    userStore.$patch( {
        user : {
            name:'一叶知秋',
            rank:5000,
            fans:10000,
        }
    })
}

</script>

<style scoped>
    .outer{
        height: 250px;
        overflow: hidden;
    }
    .user{
        height: 240px;
        width: 150px;
        margin: 5px;
        background-color: #c6e6e8;
        text-align: center;
        float: left;
        border-radius: 5px;
        box-shadow: 0 0 5px black;
    }

    .user-inf{
        width: 150px;
        text-align: left;
        padding: 0 20px;
    }

    span{
        font-size: 16px;
        color: #000;
        font-size: 10px;
        font-family: 微软雅黑;
        font-weight: 600;        
    }

    .thoto{
        height: 50px;
        border-radius: 40%;
        margin: 10px;
    }

    .content{
        width:630px;
        height: 240px;
        /* background-color: #c7d2d4; */
        float: right;
        margin: 5px;        
    }
    textarea{
        width:630px;
        height: 200px;
        background-color: #c6e6e8;
        border: none;
        padding: 5px;
        border-radius: 5px;
        box-shadow: 0 0 5px black;
    }
    button{
        background-color: #c6e6e8;
        height: 30px;
        margin-top: 5px;
        margin-right: 10px;
        border-radius: 5px;
        padding: 5px;
        box-shadow: 0 0 5px black;
        border: none;
        transition: box-shadow 0.3s;
    }
    button:hover{
        box-shadow: 0 0 8px rgb(132, 132, 132);
    }
    button:active{
        box-shadow: 0 0 10px rgb(255, 255, 255);
    }
</style>
相关推荐
于指尖飞舞7 分钟前
在vue3中使用datav完整引入时卡在加载页面的解决方法
vue3·报错·datav
桂月二二4 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
CodeClimb5 小时前
【华为OD-E卷 - 第k个排列 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
沈梦研5 小时前
【Vscode】Vscode不能执行vue脚本的原因及解决方法
ide·vue.js·vscode
hunter2062066 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb6 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角6 小时前
CSS 颜色
前端·css
轻口味6 小时前
Vue.js 组件之间的通信模式
vue.js
九酒6 小时前
从UI稿到代码优化,看Trae AI 编辑器如何帮助开发者提效
前端·trae
浪浪山小白兔7 小时前
HTML5 新表单属性详解
前端·html·html5