vue3组件通信方式

目录

一、父子组件通信

 1.父传子:props

2.子传父:自定义事件

注意区分原生事件与自定义事件中的$event;

3.父子组件相互通信:v-model

4.ref、$parent

二、任意组件间通信

1.mitt

2.pinia

三、祖--->孙

1.attrs

2.祖<--->孙直接通信  provide、inject

四、插槽slot


一、父子组件通信

 1.父传子:props
//父组件
<template>
    <div class="parent">
        父组件数据:{{ forChild1Car }}
        <Child1 :car="forChild1Car"/>
        <Child2/>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import Child1 from './Child1/index.vue';
import Child2 from './Child2/index.vue';

const forChild1Car = ref<number>(5);
</script>
// 子组件
<template>
    <div class="child1">
            // 接收数据
        <span>父组件发来数据:{{ car }}</span>
    </div>
</template>
<script>
import { defineProps } from 'vue';
defineProps(['car']);
</script>

2.子传父:自定义事件
//父组件
<template>
    <div class="parent">
        父组件数据:{{ forChild1Car }} 
        <p>父组件收到Child2的数据:</p>
        <span v-for="item in Child2Toy" :key="item">{{ item }}</span>
        <Child1 :car="forChild1Car" />

           //自定义事件sendToy
        <Child2 @sendToy="Child2Toy=$event"/>

    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import Child1 from './Child1/index.vue';
import Child2 from './Child2/index.vue';
const forChild1Car = ref<number>(5);
const Child2Toy = ref<string[]>([]);
</script>
//子组件
<template>
    <div class="child2">
        <p>child2 拥有玩具:</p>
        <span v-for="item in toy" :key="item">{{ item }}</span>

        //点击事件调用sendtoy
        <el-button type="primary" @click="sendtoy">发送给父组件</el-button>

    </div>
</template>

<script setup lang="ts">
// 引入自定义事件
import { defineEmits } from 'vue';
let $emit=defineEmits(['sendToy']);

const toy = ['奥特曼', '电击小子', '铠甲勇士'];

// 调用sendtoy函数时触发自定义事件sendToy
const sendtoy = () => {
    $emit('sendToy', toy);
}

</script>

注意区分原生事件与自定义事件中的$event;
  • 原生事件:

    • 事件名是特定的(clickmosueenter等等)

    • 事件对象$event: 是包含事件相关信息的对象(pageXpageYtargetkeyCode

  • 自定义事件

    • 事件名是任意名称

    • 事件对象$event: 是调用emit时所提供的数据,可以是任意类型!!!

3.父子组件相互通信:v-model
//自定义一个输入框,要求双向数据绑定
<MyInput v-model:abc=username/>
//相当于
<MyInput :abc=username @update:abc="username=$event">

MyInput的内部

<template>
  <div class="box">
    //input相当于MyInput的子组件
    <input 
       type="text" 
        //父组件数据传来,动态渲染到输入框(父传子)                   (数据->视图)
       :value="abc"                                                     +
        //一旦输入,触发abc事件,将输入数据传递给父组件(子传父)       (视图->数据)=双向绑定
       @input="emit('update:abc',$event.target.value)"
    >
  </div>
</template>

<script setup lang="ts" name="MyInput">
  // 接收props
  defineProps(['abc'])
  // 声明事件
  const emit = defineEmits(['update:abc'])
</script>
4.ref、$parent

ref:(子->父)可以获取真实的DOM节点,可以获取到子组件实例VC,在父组件中拿到子组件的实例,那么就可以操作子组件的属性及方法了,默认情况下是不能拿到的,子组件需要对外暴露。

//父组件
<template>
  <div class="box">
    <h1>我是父组件:{{money}}</h1>
    <button @click="handler">找我的儿子借10元</button>
    <Son ref="son"></Son>
  </div>
</template>
 
<script setup lang="ts">
//ref:可以获取真实的DOM节点,可以获取到子组件实例VC
//$parent:可以在子组件内部获取到父组件的实例
//引入子组件
import Son from './Son.vue'
import { ref } from 'vue'
//父组件钱数
let money = ref(100000000)
//获取子组件的实例
let son = ref()
//父组件内部按钮点击回调
const handler = () => {
  console.log(son.value)//打印子组件的实例对象
  money.value += 10
  //儿子钱数减去10
  son.value.money -= 10
  son.value.fly()
}
})

//子组件

<template>
  <div class="son">
    <h3>我是子组件:{{money}}</h3>
  </div>
</template>
 
<script setup lang="ts">
import { ref } from 'vue'
//儿子钱数
let money = ref(666)
const fly = () => {
  console.log('我可以飞')
}
//组件内部数据对外关闭的,别人不能访问
//如果想让外部访问需要通过defineExpose方法对外暴露
defineExpose({
  money,
  fly
})
</script>

$parent:可以在子组件内部获取父组件实例。同样父组件中数据需对外暴露

//父组件

<Dau></Dau>
<script setup lang="ts">
//$parent:可以在子组件内部获取到父组件的实例
//引入子组件
import Dau from './Daughter.vue'
import { ref } from 'vue'
//父组件钱数
let money = ref(100000000)
//对外暴露
defineExpose({
  money
)}

//子组件

<template>
  <div class="dau">
    <h1>我是子组件{{money}}</h1>
    <button @click="handler($parent)">点击我父组件给我10000元</button>
  </div>
</template>
 
<script setup lang="ts">
import { ref } from 'vue'
//子组件钱数
let money = ref(999999)
//子组件按钮点击回调
const handler = ($parent: any) => {
  console.log($parent)
  money.value += 10000
  $parent.money -= 10000
}

二、任意组件间通信

1.mitt

注意:绑定的事件,一定要在组件卸载时,解绑。

安装mitt

npm install --save mitt

新建文件:src\utils\emitter.ts

import mitt from "mitt";
let emitter=mitt();

接收数据方:emitter.on绑定事件

import { onUnmounted } from 'vue'
//引入emitter对象
import emitter from "src/utils/emitter.ts" 
//点击按钮回调
const handler = () => {
// xxx即为事件类型
  emitter.emit('xxx', { car: 'su7' })
}
//组件卸载时解绑
onUnmounted(() => {
  emitter.off('xxx')
})

传递数据方:emitter.emit触发事件,传递参数即发送数据

import emitter from "src/utils/emitter.ts"
 
import { onMounted } from 'vue'

//组件挂载完毕的时候,当前组件绑定一个事件,接受将来组件传递的数据
onMounted(() => {
  //第一个参数:即为事件类型  第二个参数:即为事件回调
  $bus.on('xxx', (car) => {
    console.log(car)
  })
})

示例:

//child1
<template>
  <div class="child1">
    <p>Child1</p>
    <span>父组件发来数据:{{ car }}</span>
    <span>Child2发来数据:{{ recieveChild2Toy }}</span>
  </div>
</template>

<script setup lang="ts">
import { defineProps } from 'vue'
defineProps(['car'])
import { ref } from 'vue'
import { emitter } from '@/mitt'
let recieveChild2Toy = ref<string[]>()
emitter.on('sendToy', (toy: any) => {
  recieveChild2Toy.value = toy
})
</script>



//child2
<template>
  <div class="child2">
    <p>child2 拥有玩具:</p>
    <span v-for="item in toy" :key="item">{{ item }}</span>
    <el-button type="primary" @click="sendToy">发送给父组件</el-button>
    <el-button type="primary" @click="handle">发送给Child1</el-button>
  </div>
</template>

<script setup lang="ts">
//与父组件的自定义事件通信
import { defineEmits } from 'vue'
let $emit = defineEmits(['sendToy'])
const sendToy = () => {
  $emit('sendToy', toy)
}

//与child1的mitt通信
import { emitter } from '@/mitt'
const toy = ['奥特曼', '路飞', '山治']
const handle = () => {
  emitter.emit('sendToy', toy)
}
</script>

2.pinia

三、祖--->孙

1.attrs

$attrs是一个对象,包含所有父组件传入的标签属性。

$attrs会自动排除defineProps中声明的属性。

//父组件
<Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateD="updateD"/>

<script setup lang="ts" name="Father">
	import Child from './Child.vue'
	import { ref } from "vue";
	let a = ref(1)
	let b = ref(2)
	let c = ref(3)
	let d = ref(4)

	function updateD(value){
		D.value = value
	}
</script>
//子组件
<template>
	<div class="child">
		<h3>子组件</h3>
		<GrandChild v-bind="$attrs"/>
	</div>
</template>

<script setup lang="ts" name="Child">
import {defineProps} from 'vue';
import GrandChild from './GrandChild.vue';
defineprops(['a','b','c']);
console.log(a,b,c);   //子组件defineProps了,不会通过attrs传给孙组件
</script>
<template>
	<div class="grand-child">
		<h3>孙组件</h3>
        <h4>d:{{ d }}</h4>
		<h4>x:{{ x }}</h4>
		<h4>y:{{ y }}</h4>
		<button @click="updateD(888)">点我更新D</button>
	</div>
</template>

<script setup lang="ts" name="GrandChild">
	defineProps(['x','y','updateD'])
</script>
2.祖<--->孙直接通信  provide、inject
//父组件
<template>
  <div class="father">
    <h3>父组件</h3>
    <h4>资产:{{ money }}</h4>
    <h4>汽车:{{ car }}</h4>
    <button @click="money += 1">资产+1</button>
    <button @click="car.price += 1">汽车价格+1</button>
    <Child/>
  </div>
</template>

<script setup lang="ts" name="Father">
  import Child from './Child.vue'
  import { ref,reactive,provide } from "vue";
  // 数据
  let money = ref(100)
  let car = reactive({
    brand:'奔驰',
    price:100
  })
  // 用于更新money的方法
  function updateMoney(value:number){
    money.value += value
  }
  // 提供数据
  provide('moneyContext',{money,updateMoney})
  provide('car',car)
</script>



//2.孙组件
<template>
  <div class="grand-child">
    <h3>我是孙组件</h3>
    <h4>资产:{{ money }}</h4>
    <h4>汽车:{{ car }}</h4>
    <button @click="updateMoney(6)">点我</button>
  </div>
</template>

<script setup lang="ts" name="GrandChild">
  import { inject } from 'vue';
  // 注入数据
 let {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(x:number)=>{}})
  let car = inject('car')
</script>

ps:子组件中需要编写任何东西,所以说是祖孙”直接”通信。

四、插槽slot

先看以下代码

//父组件
<template>
  <div class="Father">
    <Child> 
        //当我们在这里插入标签
        <span>你好</span>
        //浏览器不知道将span放在Child中什么位置,所以span不会显示
    </Child>
  </div>
</template>

//子组件Child
<template>
  <div class="child">
    hello World
  </div>
</template>

插槽解决了此问题

//父组件
<template>
  <div class="Father">
    <Child> 
        <span>你好</span>    //如果Child只有一个默认插槽,那么这里所有东西都会放入插槽
    </Child>
  </div>
</template>

//子组件Child
<template>
  <div class="child">
    <p>hello</p>
    <slot>我是插槽默认值,如果父组件不给我传东西这行字就会显示</slot>
    <p>world</p>
  </div>
</template>

这样,span中的内容就会被放到插槽中。

<Child>当有多个内容要放到这里</Child>,则需用到具名插槽

//父
<template>
  <div class="parent">
    <Child>
      <template v-slot:nihao>
        <span>插入:你好</span>
      </template>
      <template v-slot:shijie>
        <span>插入:世界</span>
      </template>
    </Child>
  </div>
</template>


//子
<template>
  <div class="child1">
    <p>hello</p>
    <slot name="nihao">我是槽默认值,如果父组件不给我传东西这行字就会显示</slot>
    <p>world</p>
    <slot name="shijie">我是槽默认值,如果父组件不给我传东西这行字就会显示</slot>
    <!-- 没用到的插槽,显示默认内容 -->
    <slot name="unUse">我是槽默认值,如果父组件不给我传东西这行字就会显示</slot>
  </div>
</template>

如何使用插槽传数据:

//父组件中:
      <Game v-slot="params">
      <!-- <Game v-slot:default="params"> -->
      <!-- <Game #default="params"> -->
        <ul>
          <li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
        </ul>
      </Game>

//子组件中:
      <template>
        <div class="category">
          <h2>今日游戏榜单</h2>
            //通过slot传参
          <slot :games="games" a="哈哈"></slot>
        </div>
      </template>

      <script setup lang="ts" name="Category">
        import {reactive} from 'vue'
        let games = reactive([
          {id:'asgdytsa01',name:'英雄联盟'},
          {id:'asgdytsa02',name:'王者荣耀'},
          {id:'asgdytsa03',name:'红色警戒'},
          {id:'asgdytsa04',name:'斗罗大陆'}
        ])
      </script>

可以看出,数据在组件的自身,但根据数据生成的结构由使用者来决定。

  • 18
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值