IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vue组件间数据通信 -> 正文阅读

[JavaScript知识库]Vue组件间数据通信

摘要

这是一篇对Vue组件间通信方式的总结笔记,包含了Vue2 及 Vue3 常用通信方式,之所以写下来是因为这既是对自身学习的一种复盘,同时也希望对有需要的朋友提供一些便利。

组件通信的基础模型

在这里插入图片描述

根据上面这幅图,可以把组件之间需要通信的方式分为三大类,分别是:

  • 父子组件通信
  • 兄弟组件通信
  • 祖孙组件通信

组件通信方式详解

父子组件通信

  • Vue2 props / $emit ( $on )

    父组件

    <!-- template 模板 -->
    <cptx :val="123" @changeTxt="changeTxtFn">11</cptx>
    
    // 父组件
    methods: {
        changeTxtFn (data) {
          console.log('子组件传递过来的数据:', data)
        }
    },
    

    子组件

    <button @click="handleClick">向父组件传递数据</button>
    
    export default {
      name: 'cptx',
      data () {
        return {
          bc: 11
        }
      },
      props: ['val'],
      mounted () {
      	// 可以直接通过this.val获取到数据,模板中直接使用,例如:{{val}}
      	console.log('this.val::', this.val)
    	
    	// 主要起到一个对事件的监听,可以获取触发事件后的回调参数
    	this.$on('changeTxt', (val) => {
          console.log('this.$on监听事件:', val)
        })
      },
      methods: {
        handleClick () {
          this.$emit('changeTxt', '这是子组件向父组件穿的数据123')
        }
      }
    }
    

    子组件通过props接收父组件传递的参数:
    在这里插入图片描述

    子组件通过$emit向父组件传递数据,并通过$on实现对事件的监听

    在这里插入图片描述

  • Vue3 defineProps 、defineEmits、defineExpose
    父组件要访问子组件自身的定义的数据以及方法,直接通过Vue2中 ref 方式是获取不到的,需要通过defineExpose 暴露出去

    父组件

    <!-- template 模板 -->
    <template>
    	<PropsModal  ref="abc" :propsNum="123" @changeParentPrice="handleChange">11</PropsModal>
    	<button @click='handleClick'>点击</button>
    </template>
    
    <script>
    import { defineComponent, ref } from 'vue'
    import PropsModal from '../components/propsModal.vue'
    
    export default defineComponent({
        setup() {
    		
    		const abc = ref(null);
       		
       		// 父组件定义函数获取子组件通过 defineEmit 传递出来的参数
            const handleChange = (data) => {
                console.log('data::', data)
            }
    
    		// 父组件获取输出子组件通过 defineExpose 暴露出来的数据及函数
    		const handleClick = function () {
    
                console.log('refAbc::', abc)
                console.log('refAbc::', abc.value)
                console.log('refAbc::', abc.value.bc)
                console.log('refAbc::', abc.value.msg)
                const {person:{value:{name,age}}} = toRefs(abc.value._obj);
                
                console.log('refAbc::', toRefs(abc.value._obj))
                console.log('refAbc::', toRefs(abc.value._obj).person.value)
                console.log('refAbc::', name,age)
                console.log('refAbc::', abc.value.btn)
    
    			// 调用暴露出去的函数
                abc.value.btn()
            }
    
    		// 返回一个对象
            return {
                handleChange,
                abc,
                handleClick 
            }
        },
        components: {
            PropsModal
        }
    })
    </script>
    

    子组件

    <script setup>
    // 按需引入
    import {defineProps, defineEmits, defineExpose, toRefs, toRef} from 'vue';
    
    // 通过defineProps来接收父组件传递过来的参数
    let props = defineProps({
        // 方式一
        // propsNum: String
    
        // 方式二
        propsNum: {
            // type: String,  // 设置参数类型方式一
            type: [String, Number],  // 设置参数类型方式二
            default: '2',  // 设置默认值
        	required: true // 是否为必须项,一般不予default同时使用
        }  
    })
    
    // 如果在函数中使用,需要将响应式对象转换为普通对象ref, 如果是在模板中使用,则直接使用,例如{{propsNum}}
    const {propsNum} = toRefs(props);
    console.log('propsNum::', propsNum.value)
    
    
    // ----------------------------------------- 分隔符 ----------------------------------------------	
    
    
    /* 
    在子组件中使用 defineEmits 来声明 emits
    defineEmits函数参数是个数组,数组内容是自定义函数名称
    不能定义在局部函数中,必须定义在全局中,否则会报语法错误,例如:Uncaught ReferenceError: defineEmits is not defined
    */
    const emits = defineEmits(['changeParentPrice'])
    
    // 获取input事件下所输入的值
    const changeCarName = (e) => {
        const currentVal = e.target.value;
        // emits函数第一个参数是自定义事件名称,第二个参数是需要传递的内容
        emits('changeParentPrice', currentVal)
    }
    
    
    // ----------------------------------------- 分隔符 ----------------------------------------------	
    
    const bc = '1111111';
    const msg = ref('子组件自定义数据');
    const _obj = reactive({
        person: {
            name: '张三',
            age: 18
        }
    });
    const btn = () => {
        console.log('打印输出msg', msg.value + '----')
    };
    
    // 通过defineExpose暴露子组件需要暴露出去的数据及函数, 如果没有通过defineExpose暴露出去,父组件将不能获取子组件的数据及函数
    defineExpose({
        bc,
        msg,
        _obj,
        btn
    })
    </script>
    

    defineEmits不能定义在局部函数中,必须定义在全局中,否则会报语法错误,例如:Uncaught ReferenceError: defineEmits is not defined

    在这里插入图片描述

    父组件无法获取通过ref获取子组件暴露出来的数据及函数
    在这里插入图片描述

    父组件可以获取通过 ref获取子组件通过 defineExpose暴露出来的数据及函数

    在这里插入图片描述

  • $parent(子组件使用) / $children(父组件使用)
    不太建议使用 this. p a r e n t 和 t h i s . parent 和 this. parentthis.children,因为会增加维护成本

    // 父组件
    data () {
       return {
          aa: 112,
          changeText: '祖先组件通过provide传递给后代组件的参数'
       }
    },
    mounted () {
    	// 父组件通过 this.$children 访问子组件实例对象
    	console.log('父组件使用 this.$children:', this.$children)
    },
    methods: {
        homeMethods () {
          console.log('父组件')
        },
        changeNun () {
          this.changeText += '@ | '
          console.log('this.changeText::', this.changeText)
        },
        changeTxtFn (data) {
          console.log('子组件传递过来的数据:', data)
        }
    },
    

    在这里插入图片描述

    // 子组件
    data () {
        return {
          bc: 11
        }
      },
    mounted () {
    	// 子组件通过 this.$parent 访问父组件实例对象
    	console.log('子组件使用 this.$parent:', this.$parent)
    },
    methods: {
        childMethods1 () {
          console.log('子组件1')
        },
        childMethods2 () {
          console.log('子组件2')
        },
        childMethods3 () {
          console.log('子组件3')
        },
        handleClick () {
          this.$emit('changeTxt', '这是子组件向父组件穿的数据123')
        }
    },
    

在这里插入图片描述

  • this.$refs(父组件使用)
    通过this.refs可以获取子组件实例对象,可以访问子组件data中的数据以及方法

    <!-- template 模板 -->
    <cptx ref="aa" :val="123">11</cptx>
    <cptx ref="bb" :val="456">11</cptx>
    
    // 父组件
    mounted () {
      	console.log('this.$refs:', this.$refs);
    }
    
    /*
    输出结果:
    this.$refs: {aa: VueComponent, bb: VueComponent}
    aa: VueComponent {_uid: 4, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
    bb: VueComponent {_uid: 5, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
    [[Prototype]]: Object
    */
    
    // 子组件
    
    
  • $attrs / $listeners
    $attrs用于多层次组件传递参数(组件标签的attribute,class和style除外),爷爷辈组件向孙子辈组件传递参数(注:参数不能被父辈prop识别,一旦被父辈prop识别且获取,则孙子辈组件不能获取到该参数

    $listeners用于多层次组件传递事件监听器,爷爷辈组件向父辈、孙子辈、曾孙子辈……组件传递事件(与 $attrs 不同,不存在半路被拦截的情况

    【注意】表示在Vue3的虚拟DOM中,事件监听器现在只是以on为前缀的attribute, 这样它就成为了$attrs对象的一部分,因此listeners被移除

    查看官方说明

    • 父组件
    <!-- template 模板 -->
    <template>
    	<div class="home-wrp">
    		<h1>{{ aa }}</h1>
            <cptx :val="123" :msg1="msg1" :msg2="msg2"  @handleClick1="handleClick1">11</cptx>
        </div>
    </template>
    
    export default {
      name: 'home',
      data () {
        return {
        	aa: '这是祖先组件',
    		msg1: '这是msg1的消息#####',
    		msg2: '这是msg2的消息@@@@@',
    		changeText: '祖先组件通过provide传递给后代组件的参数'
        }
      },
      methods: {
        handleClick1(data) {
          console.log('执行祖先组件:', data)
        }
      },
    }
    
    • 子组件
      作为父组件和孙子组件的传递中介(中间层),在儿子组件中给孙子组件添加v-bind="$attrs"v-on="$listeners",这样孙子组件才能接收到数据和事件。

      注意:在子组件中同样可以传递私有数据(bnc)以及事件,但是需要注意的是,单独传递的私有数据名称与祖先组件传递过来的数据对象里面的名称同名,那么将会把祖先组件传递进来的数据给覆盖掉,如果子组件传递的是同名的事件,却不会覆盖祖先组件传递的事件,但是会依次冒泡向上执行同名事件。

    <!-- template 模板 -->
    <template>
      <div>
      	<h1>{{ bc }}</h1>
      	<!-- 此处有重点,子组件先拦截获取msg1 -->
        <h1>props传参:{{msg1}}</h1>
        <button @click="handleClick2">执行父组件</button>
        <!-- 此处有重点,v-bind 和 v-on 不能采用简写 :或者 @ -->
        <PageModal1 :bnc="180"  :val="11111111111" v-bind="$attrs" v-on="$listeners"   @handleClick1="handleClick4"></PageModal1>
      </div>
    </template>
    
    <script>
    import PageModal1 from './pageModal1.vue'
    export default {
      name: 'cptx',
      data () {
        return {
          bc: '这是子组件'
        }
      },
      props: ['msg1'],  // 利用props接收父组件传递过来的参数
      components: {
        PageModal1
      },
      methods: {
        handleClick2 (data) {
          console.log('执行父组件')
          this.$emit('handleClick1')
        },
        handleClick4 () {
          console.log('执行父组件4444')
        }
      }
    }
    </script>
    
    
    • 孙子组件
      在孙子组件中只能使用props接收从祖先组件传递过来的数据,一定要使传递的key保持同名。
      例如在这个例子中祖先组件传递的 msg1, msg2, val
    <!-- template 模板 -->
    <template>
      <div>
        <h1>{{ mm }}</h1>
        <h1>利用props获取祖先组件传递进来的val::{{val}}</h1>
        <h1 style="color: red">利用props获取祖先组件传递进来的msg1::{{msg1 || '获取不到msg1数据, 因为被子组件拦截先获取'}}</h1>
        <h1>利用props获取祖先组件传递进来的msg2::{{msg2}}</h1>
        <button @click="handleClick3">执行孙子组件</button>
      </div>
    </template>
    
    <script>
    export default {
      name: 'pageModal1',
      data () {
        return {
          mm: '这是孙子组件'
        }
      },
      props: ['val', 'msg2'],
      methods: {
        handleClick3 () {
          console.log('执行孙子组件')
          // 触发祖先组件传递的函数,并给到一个返回值'123'
          this.$emit('handleClick1', '123')
        }
      }
    }
    </script>
    

    在这里插入图片描述

    后面这张图看着可能有些乱,请各位同学结合代码一起看,另外上面这段示例是基于 Vue 2.5.2版本。

兄弟组件通信

  • vuex
  • pinia
    相关笔记一览
  • eventBus
  • $attrs / $listeners
    主要通过消息订阅,发布的模式获取对应的数据

祖孙组件通信

  • 兄弟组件同样适用于祖孙组件

  • provide / reject

    • vue2 中 provide 和 reject 的使用
      注意:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。

      // 父组件
      methods: {
          changeNun () {
            this.changeText += '@ | '
            console.log('this.changeText::', this.changeText)
          }
        },
        
        /*
         写法1(正确写法)
      	provide: {
      		cd: '这是要传递的数据'
      	},
        */
        
        /* 
        写法2(错误写法)
        provide: {
        	cd: this.changeText
        },
        */
        
        // 写法2(正确写法)
        provide () {
          return {
            cd: this.changeText
          }
        }
      
      // 后代组件
      export default {
        name: 'pageModal1',
        data () {
          return {}
        },
        inject: ['cd'],
        mounted () {
          console.log('this.cd::', this.cd)
        },
      }
      
      /*
      	点击按钮后打印输出
      	home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | 
      	home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | 
      	home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | 
      	home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | @ | 
      	home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | @ | @ | 
      	home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | @ | @ | @ | 
      	home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | @ | @ | @ | @ | 
      	home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | @ | @ | @ | @ | @ | 
      	home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | @ | @ | @ | @ | @ | @ | 
      	home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | @ | @ | @ | @ | @ | @ | @ | 
      */
      

      在这里插入图片描述

    • vue3 中provide 和 reject 的使用

      // 祖先组件
      <script setup>
      	// 引入函数
      	import {provide, reactive} from 'vue'
      	
      	// 定义响应式数据
      	let car = reactive({car: '奔驰',price: '50w'});
      	
      	// 利用provide传递参数,params:为对象key, car:为对象key的值
      	provide('params', car)
      </script>
      
      // 孙子组件(后代组件)
      <script>
      // 按需引入函数
      import { defineComponent, inject, toRefs } from 'vue'
      
      // 采用另外一种实例化组件方式
      export default defineComponent({
          setup() {
          	// 接收祖先组件传递的参数对象
              let myParams = inject('params');
      		
      		// 将响应式对象转为普通对象,这样就能够访问到传递的参数
              const{car, price} = toRefs(myParams);
      
              console.log('测试provide/inject::', myParams,car.value, price.value)
      
      		// 将解构的数据return回去,可以直接在模板中使用
              return {
                  car,
                  price
              }
          },
      })
      </script>
      

      在这里插入图片描述

关于Vue数据通信的方式就暂时总结到这里,后面还会保持更新。

如果本篇文章对各位同学有所帮助,欢迎大家点赞收藏

当然了,如果有不足之处,烦请各位同学能够指正,赵花花将不胜感激。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-08-06 10:34:49  更:2022-08-06 10:35:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/11 12:46:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码