当前位置: 首页 > article >正文

Vue3:组件通信方式

目录

1、父子组件通信 props

1.1 父传子

1.2 子传父

 2、自定义事件 custom-event

3、mitt

4、Pinia

4.1 搭建pinia环境

4.2 存储和读取数据

4.3 修改数据(三种方式)

 4.4 storeToRefs

storeToRefs的使用方法

4.5 getters

4.6 $subscribe的使用

 4.7 pinia的组合式写法

5、参考


1、父子组件通信 props

props可以实现父传子,也可以实现子传父

1.1 父传子

首先准备下面的父组件,里面包含数据,我设置为fatherName,然后再给子组件传递数据

<!-- 父组件 -->
 <template>
    <div style="height: 200px; width: 200px; background-color: aquamarine;">
      <h4>父组件,名字为{{ fatherName }}</h4>
      <!-- 将父组件的数据fatherName传递给子组件 -->
      <child :fatherName1="fatherName"></child>
    </div>
 </template>

 <script setup>
import child from './child.vue';
import { ref } from 'vue';

let fatherName = ref('张三')
</script>
<style scoped></style>

:fatherName1="fatherName"  

  • 将父组件中的fatherName变量的值(即'张三')传递给子组件的fatherName1 props。

  • 子组件可以通过fatherName1 props来接收并使用这个值

子组件代码如下:

<!-- 子组件 -->
<template>
    <div style="height: 80px; width: 200px; background-color: beige;">
        <h4>子组件,名字为{{ childName }}</h4>
        <!-- 使用传递过来的数据 -->
        <div>父亲名字为:{{ fatherName1 }}</div>
    </div>
 </template>

 <script setup>
import { ref } from 'vue';
//接受父组件传递的数据
defineProps(['fatherName1'])
let childName = ref('张小')
</script>
 <style scoped></style>

defineProps()中一定要是数组,在父组件传入多个数据给子组件的时候,我们是将多个数据写成数组的形式,即使只传入一个数据也要写成数组的形式。

其次,上述示例中,fatherName1在父子组件中的名称必须相同,否则无法实现父子组件通信的功能

效果图如下:

1.2 子传父

我们继续上面那个示例,如果子组件需要向父组件传递它自己拥有的数据childName。那么首先我们需要在父组件那创建一个方法getChildName,并把方法传递给子组件

<!-- 父组件 -->
 <template>
    <div style="height: 400px; width: 200px; background-color: aquamarine;">
      <h4>父组件,名字为{{ fatherName }}</h4>
      <div>孩子为{{ childName }}</div>
      <!-- 将父组件的数据fatherName传递给子组件,将方法传给子组件 -->
      <child :fatherName1="fatherName" :sendName="getChildName"></child>
    </div>
 </template>

 <script setup>
import child from './child.vue';
import { ref } from 'vue';

let fatherName = ref('张三')
let childName = ref('')

//接收子组件数据的方法
function getChildName(name){
   childName.value = name
}
</script>
 <style scoped></style>

 然后,我们在子组件中接收方法,并在一个合适的地方调用即可(虽然告诉父亲名字是有点离谱,但是这个不重要哈

<!-- 子组件 -->
<template>
    <div style="height: 200px; width: 200px; background-color: beige;">
        <h4>子组件,名字为{{ childName }}</h4>
        <!-- 使用传递过来的数据 -->
        <div>父亲名字为:{{ fatherName1 }}</div>
        <button @click="sendName(childName)">告诉父亲自己的名字</button>
    </div>
 </template>

 <script setup>
import { ref } from 'vue';
//接受父组件传递的数据
defineProps(['fatherName1', 'sendName'])
let childName = ref('张小')
</script>
 <style scoped></style>

效果如下:先是没有点击按钮的效果,第二张图是点击按钮后的效果

  

子传父稍微麻烦和被动一点 

 2、自定义事件 custom-event

首先明白 $event 是事件对象,一个特殊的占位符,那么我们也可以自定义一个事件对象用来父子组件间的通信

自定义事件是专门用来实现子传父的组件通信功能的

父组件的代码如下:

<!-- 父子组件通信 -->
<template>
    <div style="height: 400px; width: 200px; background-color: aquamarine;">
      <h4>父组件,名字为{{ fatherName }}</h4>
      <div>孩子为{{ childName }}</div>
      <!-- 子组件,绑定一个自定义事件abc,只要abc事件被触发,那么xyz就会被调用,abc在子组件中声明 -->
      <child @abc="xyz"></child>
    </div>
</template>

<script setup>
import child from './child.vue';
import { ref } from 'vue';

let fatherName = ref('张三')
let childName = ref('')

//name是在子组件中传递的数据决定的
function xyz(name){
   childName.value=name
}
</script>

 <style scoped></style>

子组件的代码如下:子组件的话需要使用defineEmits()来声明事件,然后在合适的地方使用emit()调用事件去触发上面父组件定义的xyz方法

<!-- 父子组件通信 -->
<template>
    <div style="height: 200px; width: 200px; background-color: beige;">
        <h4>子组件,名字为{{ childName }}</h4>
        <!-- 点击按钮触发abc事件,逗号后边是传递的数据 -->
        <button @click="emit('abc', childName)">介绍自己</button>
    </div>
</template>

<script setup>
import { ref } from 'vue';

const emit = defineEmits(['abc'])    //声明事件
let childName = ref('张小')
</script>
 <style scoped></style>

 点击按钮后呈现的效果如下:

感觉这个通信方式和第一个通信方式差不多,都是在父组件定义方法再到子组件中触发这个方法。子组件都需要声明方法并触发方法,只是略微有一些语句上的差别

在理解这个自定义事件的时候,可以将<child @abc="xyz"></child> 中的@abc="xyz"类比成@click="xyz"来理解,只是click点击事件是官方写好的事件,abc是我们自定义的事件

3、mitt

这个方法可以在任意组件中使用。

首先需要安装mitt,执行指令:npm i mitt,然后在utils文件夹下创建emitter.js文件,文件中的内容写下以下内容:

// 引入mitt
import mitt from 'mitt'
//调用mitt得到emitter,可以绑定和触发事件
const emitter = mitt()
export default emitter

然后在需要组件通信的vue组件中加入 import emitter from './utils/emitter' 这句话

我们需要明白emitter.on()是绑定事件,emitter.off()是解绑事件,emitter.emit()触发事件,emitter.all()是拿到所有绑定的事件

提供数据的组件要触发事件,接收数据的组件要绑定事件

接下来我们看示例即可:示例是两个孩子,孩子1要问孩子2的名字

孩子1代码示例:

<!-- 孩子1 -->
<template>
    <div style="height: 200px; width: 400px; background-color: beige;">
        <h4>孩子1,名字为{{ childName }}</h4>
        <h4>孩子2的名字为:{{ friendName!=='' ? friendName : '???' }}</h4>
    </div>
</template>

<script setup>
import { onMounted, ref } from 'vue';
import emitter from '../utils/emitter';

let childName = ref('张大')
let friendName = ref('')

onMounted(()=>{
    //在组件挂载完毕的时候,让张大问张二的名字,value即接收到的childName
    emitter.on('sentFrindName',(value)=>{
        friendName.value = value
    })
})

</script>
<style scoped></style>

孩子2代码如下:

<!-- 孩子2 -->
<template>
    <div style="height: 200px; width: 400px; background-color: beige;">
        <h4>孩子2,名字为{{ childName }}</h4>
        <!-- 触发事件,把childName作为参数传递过去 -->
        <button size="small" @click="emitter.emit('sentFrindName',childName)">介绍自己</button>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import emitter from '../utils/emitter';

let childName = ref('张二')
</script>
<style scoped></style>

 点击按钮的网页效果如下:

 

mitt是非常轻量级的,跟自定义事件比较相似,只是不用受限制于父子组件中,在任意组件中都能实现组件通信,但是在多人开发中难以追踪事件流,不适合大型项目。

  • 对于复杂项目,建议使用状态管理库(如 Vuex 或 Pinia)。

  • 对于父子组件通信,优先使用 props 和 emit

感觉mitt的位置很尴尬啊,但是自己做小型项目开发用这个会方便一点

4、Pinia

官网链接:简介 | Pinia (vuejs.org)

pinia是集中式状态管理,vue2项目比较常用的是vuex,但是vue3项目现在比较推荐pinia,更加符合直觉

多个组件共有的数据才用到pinia,一个组件独有的数据就不建议写到pinia上

4.1 搭建pinia环境

第一步npm install pinia  --->在控制台中输入命令安装pinia

第二步:在main.js文件中,引入创建和安装pinia

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
//引入pinia
import { createPinia } from 'pinia'

const app = createApp(App)
//创建pinia,最好在app创建完之后
const pinia = createPinia()
//安装pinia
app.use(pinia)
app.mount('#app')

4.2 存储和读取数据

在src文件创建一个名为store的文件夹,在store文件夹下新建xxx.js文件,xxx的命名应该体现出来这个js文件写的内容与什么相关,达到望文知意的效果。数据是直接存储到这个xxx.js文件中,不用在组件中定义存储

 因为示例仍然是张大问张二名字,所以我将在store文件下新建name.js

//name.js文件
import { defineStore } from "pinia";

export const useNameStore = defineStore('child2',{
    //真正存储数据的地方
    state(){
        return {
            child2Name:'张二'
        }
    },
})
  • defineStore: 这是 Pinia 提供的一个函数,用于定义一个状态存储(store)。每个 store 都有一个唯一的名称,这里是 'child2'

  • state: 这是一个函数,返回一个对象,这个对象就是 store 的状态。在这个例子中,state 返回了一个对象,其中包含一个属性 child2Name,初始值为 '张二'

 child1组件:

<!-- 孩子1 -->
<template>
    <div style="height: 200px; width: 400px; background-color: beige;">
        <h4>孩子1,名字为{{ childName }}</h4>
        <h4>孩子2的名字为:{{ nameStore.child2Name }}</h4>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import { useNameStore } from '../store/name';

let childName = ref('张大')
const nameStore = useNameStore()

</script>
<style scoped></style>
  • useNameStore: 这是之前定义的 Pinia store。通过调用 useNameStore(),可以在组件中访问和修改 store 中的状态。

  • nameStore: 这是 store 的一个实例,通过它可以直接访问 store 中的状态,比如 nameStore.child2Name

child2组件同理获得张二自己的名字,这里就不展示了。

相当于张大想要知道张二名字,不需要通过询问张二,直接问第三方pinia即可知道,张二显示自己的名字也需要从第三方pinia中拿到

4.3 修改数据(三种方式)

比如说我们张二要改名字,改名为张三

第一种:直接在组件在获取后修改即可

<!-- 孩子2 -->
<template>
    <div style="height: 200px; width: 400px; background-color: beige;">
        <h4>孩子2,名字为{{ nameStore.child2Name }}</h4>
        <button size="small" @click="changeName">我要改名</button>
    </div>
</template>

<script setup>
import { useNameStore } from '../store/name';

const nameStore = useNameStore()

function changeName(){
    //直接修改即可
    nameStore.child2Name = "张三"
}

</script>
<style scoped></style>

效果如下:(点击按钮前后对比)

     

第二种:适合数据多的时候用

 给孩子多写一个数据,school表示就读学校

两个文件写一块了,大家注意区别
//name.js文件
import { defineStore } from "pinia";

export const useNameStore = defineStore('child2',{
    //真正存储数据的地方
    state(){
        return {
            child2Name:'张二',
            school:"人民小学"
        }
    },
})


<!--child2.vue文件-->
<!-- 孩子2 -->
<template>
    <div style="height: 200px; width: 400px; background-color: beige;">
        <h4>孩子2,名字为{{ nameStore.child2Name }}</h4>
        <h4>就读学校{{ nameStore.school }}</h4>
        <button size="small" @click="change">我要改名转学</button>
    </div>
</template>

<script setup>
import { useNameStore } from '../store/name';

const nameStore = useNameStore()
//修改数据
function change(){
    //批量修改
    nameStore.$patch({
        child2Name:'张三',
        school:'幸福小学'
    })
}

</script>
<style scoped></style>

 第三种:最麻烦的一种

name.js文件如下: 

import { defineStore } from "pinia";

export const useNameStore = defineStore('child2',{
    //真正存储数据的地方
    state(){
        return {
            child2Name:'张二',
            school:"人民小学"
        }
    },
    //方法, 放置的是一个个的方法,用于响应组件中的“动作”
    actions:{
        changeName(name, sch){
            //修改数据,用this来拿到需要修改的数据然后赋值即可
            this.child2Name=name
            this.school=sch
        }
    }
})

child2.vue文件如下:

<!-- 孩子2 -->
<template>
    <div style="height: 200px; width: 400px; background-color: beige;">
        <h4>孩子2,名字为{{ nameStore.child2Name }}</h4>
        <h4>就读学校{{ nameStore.school }}</h4>
        <button size="small" @click="nameStore.changeName('张三','幸福小学')">我要改名转学</button>
    </div>
</template>

<script setup>
import { useNameStore } from '../store/name';

const nameStore = useNameStore()

</script>
<style scoped></style>

 4.4 storeToRefs

  1. storeToRefs 的作用

    • 将 store 中的状态转换为响应式引用(ref),保持响应性

    • 避免直接解构 store 导致的状态丢失问题

  2. storeToRefs 的适用范围

    • 仅适用于 store 中的 状态(state),不适用于 getters 或 actions

    • 如果你需要解构 getters 或 actions,可以直接从 store 实例中解构

  3. 与直接使用 store 的区别

    • 直接使用 store 实例(如 nameStore.child2Name)也是响应式的

    • 使用 storeToRefs 主要是为了方便在模板中直接使用状态,而不需要每次都写 nameStore.xxx

storeToRefs的使用方法

1、导入 storeToRefs

import { storeToRefs } from 'pinia';

2、在组件中使用

import { useNameStore } from '../store/name';
import { storeToRefs } from 'pinia';

const nameStore = useNameStore();
const { child2Name, school } = storeToRefs(nameStore);

 完整代码:

<template>
    <div style="height: 200px; width: 400px; background-color: beige;">
        <h4>孩子2,名字为{{ child2Name }}</h4>
        <h4>就读学校{{ school }}</h4>
        <button size="small" @click="nameStore.changeName('张三', '幸福小学')">
            我要改名转学
        </button>
    </div>
</template>

<script setup>
import { useNameStore } from '../store/name';
import { storeToRefs } from 'pinia';

const nameStore = useNameStore();
const { child2Name, school } = storeToRefs(nameStore);
</script>

<style scoped></style>

4.5 getters

getter是 Pinia 中用于从状态(state)中派生出计算属性的函数。类似于 Vuex 中的 getter,它允许你基于 store 的状态进行复杂计算,并返回结果

定义 Getter:在 store 中通过 getters 属性定义 getter 函数。这些函数接收 state 作为第一个参数,可以访问 store 的状态

import { defineStore } from 'pinia';

export const useStore = defineStore('main', {
  state: () => ({
    count: 0,
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
});

访问 Getter:在组件或其他地方通过 store 实例访问 getter。Getter 会被缓存,只有在依赖的状态发生变化时才会重新计算 

import { useStore } from '@/stores/main';

const store = useStore();
console.log(store.doubleCount); // 输出 doubleCount 的值

Getter 传参:Getter 可以通过返回一个函数来接受参数 

getters: {
  multiplyCount: (state) => (factor) => state.count * factor,
}

 使用时:

console.log(store.multiplyCount(3)); // 输出 count * 3 的结果

4.6 $subscribe的使用

 $subscribe 是 Pinia 提供的一个方法,用于监听 store 中状态(state)的变化。它类似于 Vue 的 watch,但专门用于监听整个 store 的状态变更

 使用方式:

1、在组件或任何地方,通过 store 实例调用 $subscribe 方法,传入一个回调函数。该回调函数会在 store 的状态发生变化时触发

import { useStore } from '@/stores/main';

const store = useStore();

store.$subscribe((mutation, state) => {
  console.log('状态变化:', mutation);
  console.log('当前状态:', state);
});
  • mutation:包含状态变化的相关信息,例如:

    • type:变更类型(如 direct 直接修改,或 patch object/patch function 使用 $patch 修改)。

    • storeId:触发变更的 store 的 ID。

    • payload:传递的数据(如使用 $patch 时的参数)。

  • state:当前 store 的最新状态。

 2、监听特定状态:如果需要监听特定的状态变化,可以在回调函数中通过state或者mutation进行判断

store.$subscribe((mutation, state) => {
  if (state.count > 10) {
    console.log('count 大于 10');
  }
});

3、取消监听:$subscribe 返回一个函数,调用该函数可以取消监听

const unsubscribe = store.$subscribe((mutation, state) => {
  console.log('状态变化:', state);
});

// 取消监听
unsubscribe();

4、配置选项

$subscribe 的第二个参数是一个配置对象,支持以下选项:

  • detached: boolean:默认为 false。如果为 true,即使组件卸载,监听器也不会被自动移除

  • deep: boolean:默认为 false。如果为 true,会深度监听状态的变化

store.$subscribe(
  (mutation, state) => {
    console.log('状态变化:', state);
  },
  { detached: true, deep: true }
);

 4.7 pinia的组合式写法

相当于直接把 defineStore('xx', ()=>{}) 中的()=>{}当成setup的东西在写,只是要记得return东西出去,其他在组件中的使用方式一样没有变化

import { defineStore } from "pinia";
// 选项式写法
// export const useNameStore = defineStore('child2',{
//     //真正存储数据的地方
//     state(){
//         return {
//             child2Name:'张二',
//             school:"人民小学"
//         }
//     },
//     //方法, 放置的是一个个的方法,用于响应组件中的“动作”
//     actions:{
//         changeName(name, sch){
//             //修改数据,用this来拿到需要修改的数据然后赋值即可
//             this.child2Name=name
//             this.school=sch
//         }
//     }
// })

//组合式写法
import { ref } from "vue";
export const useNameStore = defineStore('child2',()=>{
    let child2Name = ref('张二')
    let school = ref('人民小学')

    function changeName(name, sch){
        //修改数据,用this来拿到需要修改的数据然后赋值即可
        this.child2Name=name
        this.school=sch
    }

    //不要忘记return出去
    return {child2Name, school, changeName}
})

5、参考

052.组件通信_方式1_props_哔哩哔哩_bilibili


http://www.kler.cn/a/584699.html

相关文章:

  • 【工具使用】IDEA社区版如何使用JDK原生命令:从IDEA到命令行的开发技巧
  • 完美解决ElementUI中树形结构table勾选问题
  • 商品管理中的“DeepSeek” AI赋能零售品牌释放利润空间
  • Spring Boot 常用注解的分类及简明解释
  • Spring Boot项目中集成sa-token实现认证授权和OAuth 2.0第三方登录
  • 50.HarmonyOS NEXT 登录模块开发教程(四):状态管理与数据绑定
  • 网络安全工具nc(NetCat)
  • Android7上移植I2C-tools
  • 探索 PyTorch 中的 ConvTranspose2d 及其转置卷积家族
  • SolidWorks中文完整版+教程百度云资源分享
  • 【JavaScript 】1. 什么是 Node.js?(JavaScript 服务器环境)
  • 【Flutter】第一次textEditingController.text获取到空字符串
  • 医院本地化DeepSeek R1对接混合数据库技术实战方案研讨
  • 性能优化:服务器性能影响网站加载速度分析
  • 如何从零编写自己的.NET IoT设备驱动
  • 第54天:Web攻防-SQL注入数据类型参数格式JSONXML编码加密符号闭合复盘报告
  • JVM 详解:Java 虚拟机的核心机制
  • k8s中的控制器的使用
  • DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加列宽调整功能,示例Table14_06带搜索功能的固定表头表格
  • Linux C++ 编程死锁详解