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>
相关推荐
迷雾漫步者1 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-2 小时前
验证码机制
前端·后端
燃先生._.3 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖4 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235244 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240255 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar5 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人5 小时前
前端知识补充—CSS
前端·css
GISer_Jing6 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试