05 Vue3组件间的通信-props、defineEmits、mitt、v-model、$attrs、$ref和$parent

Vue3组件通信和Vue2的区别:

  • 移出事件总线,使用mitt代替。
  • vuex换成了pinia

  • .sync优化到了v-model里面了

  • $listeners所有的东西,合并到$attrs中了

  • $children被砍掉了

常见搭配形式
image

props - 【父传子 子传父】

父传子:属性值是非函数
子传父:属性值是函数
一般都用于 父传子

父传子

父组件

<template>
  <Child :car="car" :obj="obj" :list="list" />
</template>

<script lang="ts" setup>
import { reactive, ref } from 'vue';
import Child from '@/components/Child.vue';

const car = ref('奔驰');
// 定义数据类型-对象
type personsType = {
  id: string;
  name: string;
  age?: number;
};

// 定义数据类型-数组
// type ArrayPer = Array<persons>;
type ArrayPer = personsType[];

let obj = reactive<personsType>({ id: 'asdfj01', name: '张三' });

let list = reactive<ArrayPer>([
  { id: 'asdfj01', name: '张三', age: 10 },
  { id: 'asdfj02', name: '李四' },
  { id: 'asdfj03', name: '王五' },
]);
</script>

子组件-接收数据

<template>
  <h1>接收来自父组件的值:</h1>
  数值: {{ car }}
  <hr />
  对象:{{ obj }}
  <hr />
  数组:{{ list }}
  <hr />
</template>

<script lang="ts" setup>
import { defineProps } from 'vue';
// 定义数据类型-对象
type personsType = {
  id: string;
  name: string;
  age?: number;
};

// 方式一:直接接收
// defineProps(['car','obj','list']);

// 方式一:设置数据类型
defineProps<{
  car: string;
  obj: personsType;
  list: personsType[];
}>();
</script>

子传父

父组件

<template>
 # 子传父:属性值是 函数
  <Child :getMsg="getMsg" />
</template>

<script lang="ts" setup>
import Child from '@/components/Child.vue';

function getMsg(val: string) {
  console.log('收到了来自子组件的数据:' + val);
}
</script>

子组件

<template>
  <h1>点击按钮向父组件发送数据:</h1>
  <button @click="sentMsg">传送数据</button>
  <hr />
</template>

<script lang="ts" setup>
import { defineProps } from 'vue';

// 方式一:直接接收
let props = defineProps(['getMsg']);

// // 方式一:设置数据类型
// let props = defineProps<{
//   getMsg: Function;
// }>();

function sentMsg() {
  props.getMsg('哈哈哈');
}
</script>

自定义事件--【子传父】 defineEmits

父组件--接收数据

<template>
  // customerEventName 子组件里面的 自定义事件
  <Child @customerEventName="getMsg" />
</template>

<script lang="ts" setup>
import Child from '@/components/Child.vue';

function getMsg(val: string) {
  console.log('收到了来自子组件的数据:' + val);
}
</script>

子组件-发送数据

<template>
  <h1>点击按钮向父组件发送数据:</h1>
  <button @click="sentMsg">传送数据</button>
  <hr />
</template>

<script lang="ts" setup>
// customerEventName 是自定义事件的名称
const emit = defineEmits(['customerEventName']);
function sentMsg() {
  const msg = '哈哈哈';
  // 向父组件传递参数
  emit('customerEventName', msg);
}
</script>

mitt-- 任意组件间通信

与消息订阅与发布(pubsub)功能类似,可以实现任意组件间通信

1. 安装

npm i mitt

2. 构建 src\utils\emitter.ts

// 引入mitt
import mitt from 'mitt';

// 创建emitter
const emitter = mitt();

// 创建并暴露mitt
export default emitter;

3. mitt 绑定on-调用-发布-接收数据触发emit-定义-订阅-发数据 事件

// emitter表示的是 emitter对象


// 绑定事件--调用事件
emitter.on('abc', (value) => {
  console.log('abc事件被触发', value);
});
emitter.on('xyz', (value) => {
  console.log('xyz事件被触发', value);
});

setInterval(() => {
  // 触发事件--定义事件
  emitter.emit('abc', 666);
  emitter.emit('xyz', 777);
}, 1000);


setTimeout(() => {
  // 清理事件  
  emitter.all.clear();
  
  // 移除事件
  emitter.off('abc')
  
}, 3000);

4. 一个案例

父组件--监听事件--接收数据

<template>
  <Child />
</template>

<script lang="ts" setup>
import Child from '@/components/Child.vue';
import { onMounted, onUnmounted } from 'vue';
import mittBus from '@/utils/emitter';
onMounted(() => {
  getMsg();
});

onUnmounted(() => {
  mittBus.off('customerEventName');
});

const getMsg = () => {
  mittBus.on('customerEventName', (val) => {
    console.log('收到了来自子组件的数据:' + val);
  });
};
</script>

子组件--触发事件--发数据

<template>
  <h1>点击按钮向父组件发送数据:</h1>
  <button @click="sentMsg">传送数据</button>
  <hr />
</template>

<script lang="ts" setup>
import mittBus from '@/utils/emitter';

// customerEventName 是自定义事件的名称
const sentMsg = () => {
  const msg = '哈哈哈';
  // 向父组件传递参数msg
  mittBus.emit('customerEventName', msg);
};
</script>

v-model -- 父传子 子传父

v-model的双向数据绑定

v-model的双向数据绑定本质就是 input 绑定一个value值,同时监听input的value变化,重新赋值

<template>
  <h2>父组件</h2>
  <input type="text" v-model="username" />

  <!-- v-model的双向数据绑定本质就是 input 绑定一个value值,同时监听input的value变化,重新赋值 -->
  <input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value" />
</template>

<script lang="ts" setup>
import { ref } from 'vue';
let username = ref('zhangsan');
</script>

组件标签上的v-model的本质

:moldeValueupdate:modelValue事件

  <!-- 通过 v-model 将username 传递给 Child 子组件 -->
 <Child v-model="username" />

 <!-- 组件标签上v-model的本质 -->
 <!-- 传递数据modelValue,同时绑定事件 update:modelValue -->
 <Child :modelValue="username" @update:modelValue="username = $event" />

父子互相通信

父组件--发送数据

<template>
  <h2>父组件</h2>
  <input type="text" v-model="username" />
  <!-- 通过 v-model 将username 传递给 Child 子组件 -->
  <Child v-model="username" />
</template>

<script lang="ts" setup>
import Child from '@/components/Child.vue';
import { ref } from 'vue';
let username = ref('zhangsan');
</script>


子组件-接收数据
通过 defineProps 接收 名为 modelValue 的数据,就是父组件传递过来的 username

<template>
  <hr />
  <h2>子组件</h2>
  <h3>通过v-model接收父组件的数据:</h3>
  {{ modelValue }}
  <input type="text" :value="modelValue" @input="emit('update:modelValue', (<HTMLInputElement>$event.target).value)" />
</template>

<script lang="ts" setup>
import { defineProps } from 'vue';
// 接收数据 modelValue
defineProps(['modelValue']);

// 自定义触发事件  pdate:modelValue
const emit = defineEmits(['update:modelValue']);
</script>

更换 modelValue 的命名

父组件

<template>
  <h2>父组件</h2>
  <input type="text" v-model="username" />
  <input type="text" v-model="pwd" />
  <!-- 通过 v-model 将username 传递给 Child 子组件 -->
  <Child v-model:abc="username" v-model:xyz="pwd" />
</template>

<script lang="ts" setup>
import Child from '@/components/Child.vue';
import { ref } from 'vue';
let username = ref('zhangsan');
let pwd = ref('12356');
</script>

子组件

<template>
  <hr />
  <h2>子组件</h2>
  <h3>通过v-model接收父组件的数据:</h3>
  {{ abc }}=={{ xyz }}
  <input type="text" :value="abc" @input="emit('update:abc', (<HTMLInputElement>$event.target).value)" />
  <input type="text" :value="xyz" @input="emit('update:xyz', (<HTMLInputElement>$event.target).value)" />
</template>

<script lang="ts" setup>
import { defineProps } from 'vue';
// 接收数据 modelValue
defineProps(['abc', 'xyz']);

// 自定义触发事件  pdate:modelValue
const emit = defineEmits(['update:abc', 'update:xyz']);
</script>

$attrs -- 祖孙间传递数据

$attrs 用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙

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

注意:$attrs会自动排除props中声明的属性(可以认为声明过的 props 被子组件自己“消费”了)

父组件

<template>
  <h2>父组件</h2>
  <h3>a={{ a }}</h3>
  <h3>b={{ b }}</h3>
  <h3>c={{ c }}</h3>
  <h3>num={{ num }}</h3>

  <Child :a="a" :b="b" :c="c" v-bind="{ x: 100, y: 200, z: 300 }" :updateSum="updateSum" />
</template>

<script lang="ts" setup>
import Child from '@/components/Child.vue';
import { ref } from 'vue';
let num = ref(1);
let a = ref('a');
let b = ref('b');
let c = ref('c');

const updateSum = (val: number) => {
  num.value = val;
};
</script>

子组件 : 子组件不做任何事情,直接将 $attrs 传递给孙子组件
子组件可以通过 useAttrs方法获取到组件标签标签上面传递过来的所有数据和方法

import { useAttrs } from 'vue';
const $attrs = useAttrs();
console.log($attrs);
<template>
  <hr />
  <h2>子组件不做任何事情,直接将 $attrs 传递给孙子组件</h2>
  <GrandChild v-bind="$attrs" />
</template>

<script lang="ts" setup>
import GrandChild from '@/components/GrandChild.vue';
</script>

孙组件

<template>
  <hr />
  <h2>孙子组件</h2>
  <h3>a={{ a }}</h3>
  <h3>b={{ b }}</h3>
  <h3>c={{ c }}</h3>
  <h3>x={{ x }}</h3>
  <h3>y={{ y }}</h3>
  <h3>z={{ z }}</h3>
  <button @click="updateSum(666)">点击将 num 的值 改为 666</button>
</template>

<script lang="ts" setup>
defineProps(['a', 'b', 'c', 'x', 'y', 'z', 'updateSum']);
</script>

$refs 子传父 $parent 父传子

$refs 子传父,结合ref组件标签

$refs 值为对象,包含所有被ref属性标识的DOM元素或组件实例

父组件:只可以修改 子组件向外暴露的数据

<template>
  <h2>父组件</h2>
  <!--  $refs 子组件所有的 实例对象  -->
  <button @click="getAllChilds($refs)">获取所有子组件的实例对象,并修改数据</button>
  <Child1 ref="c1" />
  <Child2 ref="c2" />
</template>

<script lang="ts" setup>
import Child1 from '@/components/Child1.vue';
import Child2 from '@/components/Child2.vue';
import { ref } from 'vue';
let c1 = ref();
let c2 = ref();

function getAllChilds(refs: any) {
  refs.c1.name = '张三三';
  refs.c2.name = '李四四';
}
</script>

子组件:必须向外暴露数据,父组件才可以修改和获取

子组件1

<template>
  <hr />
  <h2>子组件-Child1-{{ name }}</h2>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
let name = ref('张三');
// 将name向外暴露出去
defineExpose({ name });
</script>

子组件2

<template>
  <hr />
  <h2>子组件-Child2-{{ name }}</h2>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
let name = ref('李四');
// 将name向外暴露出去
defineExpose({ name });
</script>

$parent 父传子

$parent 值为对象,当前组件的父组件实例对象

父组件

<template>
  <h2>父组件</h2>
  数值num为=={{ num }}
  <Child1 />
</template>

<script lang="ts" setup>
import Child1 from '@/components/Child1.vue';
import { ref } from 'vue';
let num = ref(6);
// 必须将父组件的值暴露给子组件,子组件才可以修改父组件的值
defineExpose({ num });
</script>

子组件

<template>
  <hr />
  <h2>子组件-Child1</h2>
  <button @click="minus($parent)">将父组件num值减1</button>
</template>

<script lang="ts" setup>
function minus(p: any) {
  console.log(p.num);
  p.num -= 1;
}
</script>

provide、inject 祖孙间传递数据

实现祖孙组件直接通信

  • 在祖先组件中通过provide配置向后代所有组件提供数据
  • 在后代组件中通过inject配置来声明接收数据

父组件

<template>
  <h2>父组件</h2>
  {{ num }}==={{ person }}
  <Child />
</template>

<script lang="ts" setup>
import Child from '@/components/Child.vue';
import { ref, reactive, provide } from 'vue';
// 定义数据
let num = ref(6);
let person = reactive({
  name: '张三',
  age: 29,
});

// 更新num的方法
function updateNum(val: number) {
  num.value -= val;
}

// 父组件传值给子组件:通过 【provide】向所有后代组件提供数据
provide('moneyContext', { num, updateNum }); // 对象形式的传值
provide('personObj', person); // 直接传值
</script>
<style scoped lang="scss"></style>

子组件

<template>
  <hr />
  <h2>子组件</h2>
  {{ num }}=={{ personObj }}
  <button @click="updateData">将父组件num值减1</button>
</template>

<script lang="ts" setup>
import { inject } from 'vue';

// 接收父组件的传值,可以设置默认值
let { num, updateNum } = inject('moneyContext', { num: 0, updateNum: (X: number) => {} });
let personObj = inject('personObj', { name: '我是默认值', age: 20 });

// 子组件传值给父组件 更新num的值
function updateData() {
  updateNum(1);
}
</script>

Pinia

参考:
Pinia状态管理

插槽

参考:

posted @ 2024-04-16 18:05  songxia777  阅读(131)  评论(0编辑  收藏  举报