目录
一、父子组件通信
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;
-
原生事件:
-
事件名是特定的(
click
、mosueenter
等等) -
事件对象
$event
: 是包含事件相关信息的对象(pageX
、pageY
、target
、keyCode
)
-
-
自定义事件:
-
事件名是任意名称
-
事件对象
$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>
可以看出,数据在组件的自身,但根据数据生成的结构由使用者来决定。