Vue3组件通信的7种方法,值得收藏!
组件通信是指组件之间的数据交流和事件传递。在 Vue 3 中提供了多种方式来实现组件通信,包括:
- props
- $emit
- ref 和 defineExpose
- provide 和 inject
- v-model
- pinia
- mitt
1. props
父组件将数据传递给子组件,子组件通过props属性接收父组件传递的数据。
子组件 Child.vue:
<script setup>
// 使用 defineProps 函数来定义 Props 的类型和默认值
// defineProps 不需要引入即可直接使用
const props = defineProps({
// 变量 count 是通过父组件传递过来的
count: {
type: Number,
default: 0
}
})
</script>
<template>
<div id="child">
<h1>count: {{ count }}</h1>
</div>
</template>
上面这段代码中,使用 defineProps 函数来定义 props 的类型和默认值。
- 在 defineProps 的参数中,我们可以定义一个对象,其中的每个属性代表一个 Prop。
- 在这个示例中,我们定义了一个名为 count 的 prop,它的类型是 Number,并设置了默认值为 0。
- 然后在 template 中,我们可以直接使用 count 变量,它是通过父组件传递过来的。
父组件 Parent.vue:
<script setup>
// 引入 ref 函数,用于定义响应式数据
import { ref } from 'vue';
// 引入子组件 Child.vue
import Child from './Child.vue';
// 使用 ref 函数创建了一个响应式的变量 count,初始值为 0,该变量将用于传递给子组件
let count = ref(0)
</script>
<template>
<div id="parent">
<!-- 将 count 变量传递给子组件 Child -->
<Child :count="count"/>
</div>
</template>
上面这段代码中:
- 我们使用 ref 函数创建了一个响应式的变量 count,初始值为 0,该变量将用于传递给子组件。
- 然后在 template 中,通过 :count="count" 的方式将 count 变量传递给子组件 Child。
这样,我们就通过 props 的方式实现了一个最简单的父子组件之间的数据传递。
2. $emit
子组件通过 $emit 方法触发一个自定义事件,并传递需要的参数。父组件通过在子组件上监听对应的事件,并指定触发事件时的回调函数。
子组件 Child.vue:
<script setup>
// 使用 defineProps 函数来定义 Props 的类型和默认值
// defineProps 不需要引入即可直接使用
const props = defineProps({
// 变量 count 是通过父组件传递过来的
count: {
type: Number,
default: 0
}
})
// 使用 defineEmits 函数定义了一个名为 changeParentCount 的自定义事件。
const emit = defineEmits(['changeParentCount'])
const changeParentCount = () => {
// 通过 emit 方法触发名为 changeParentCount 的自定义事件,并将参数 5 传递给父组件。
emit('changeParentCount', 5)
}
</script>
<template>
<div id="child">
<h1>count: {{ count }}</h1>
<button @click="changeParentCount">更新父组件的count</button>
</div>
</template>
上面这段代码中:
- 我们在 template 的 button 中使用 @click="changeParentCount" 添加点击事件监听器,当按钮被点击时,将调用 changeParentCount 方法,触发父组件中的自定义事件。
- 然后使用 defineEmits 函数定义了一个名为 changeParentCount 的自定义事件。然后通过 emit 方法触发名为 changeParentCount 的自定义事件,并将参数 5 传递给父组件。
父组件 Parent.vue:
<script setup>
// 引入 ref 函数,用于定义响应式数据
import { ref } from 'vue';
// 引入子组件 Child.vue
import Child from './Child.vue';
// 使用 ref 函数创建了一个响应式的变量 count,初始值为 0,该变量将用于传递给子组件
let count = ref(0)
// 这个方法用于处理子组件中触发的自定义事件 changeParentCount,并更新父组件中的 count 变量的值。
const changeParentCount = (params) => {
count.value += params
}
</script>
<template>
<div id="parent">
<!-- 将 count 变量传递给子组件 Child -->
<!-- 监听子组件自定义事件 changeParentCount -->
<Child :count="count" @changeParentCount="changeParentCount"/>
</div>
</template>
上面这段代码中:
- 在 template 中,我们通过 @ 符号监听子组件自定义事件 changeParentCount,并在父组件中执行名为 changeParentCount 的方法。它接收一个名为 params 的参数,然后更新父组件中的 count 变量的值。
在这个示例中,当子组件触发 changeParentCount 自定义事件时,父组件可以接收到传递的参数5,然后每点击一次子组件的 button 按钮,父组件的 count 的值都递增 5。
这种方式可以通过自定义事件实现子组件向父组件传递数据,并在父组件中处理数据更新的逻辑。
3. ref 和 defineExpose
在 Vue3 中,ref 函数除了可以用于定义一个响应式的变量或引用之外,还可以获取DOM组件实例。
而 defineExpose 是用于将组件内部的属性和方法暴露给父组件或其他组件使用。通过这种方式,我们可以定义哪些部分可以从组件的外部访问和调用。
子组件 Child.vue:
<script setup>
// 引入 ref 函数,用于定义响应式数据
import { ref } from 'vue';
// 定义变量和方法
const msg = ref('我是子组件中的数据');
const childMethod = () => {
console.log('我是子组件中的方法');
}
// defineExpose 对外暴露组件内部的属性和方法,不需要引入,直接使用
// 将属性 msg 和方法 childMethod 暴露给父组件
defineExpose({
msg,
childMethod
})
</script>
上面这段代码中:
- 我们定义了一个 msg 变量和 一个 childMethod 方法。
- 然后使用 defineExpose 函数将 msg 和 childMethod 对外暴露出去。
这样,我们在父组件中就可以访问子组件的 msg 属性或者调用 childMethod 方法。
父组件 Parent.vue:
<script setup>
// 引入响应式ref
import { ref } from 'vue';
// 引入子组件 Child.vue
import Child from './Child.vue';
// 获取子组件DOM实例
const childRef = ref();
// 该方法用于获取子组件对外暴露的属性和方法
const getChildPropertyAndMethod = () => {
// 获取子组件对外暴露的属性
console.log(childRef.value.msg);
// 调用子组件对外暴露的方法
childRef.value.childMethod();
}
</script>
<template>
<div id="parent">
<Child ref="childRef"/>
<button @click="getChildPropertyAndMethod">获取子组件对外暴露的属性和方法</button>
</div>
</template>
上面这段代码中:
- 我们在 template 的子组件 Child 身上绑定了一个 ref,然后通过 const childRef = ref() 来获取子组件的 DOM 实例。
- 然后给 button 按钮绑定了一个点击事件 getChildPropertyAndMethod,该方法用于获取子组件对外暴露的属性和方法。
- 此时,点击按钮,在控制台中,我们就可以看到打印出两句话。
总结:这种方式之间的通信,主要是在子组件内部,将属性和方法暴露出去,然后在子组件中,先获取到子组件的DOM实例,然后就可以访问子组件的属性和调用子组件的方法了。
4. provide 和 inject
在 Vue 3 中,我们可以使用 provide 和 inject 实现跨组件的通信。
- provide 是在父组件中定义的方法,用于提供数据给所有子组件。 它接收两个参数,第一个参数是一个字符串或者一个 Symbol 类型的键,用于识别提供的数据。第二个参数是要提供的数据本身。这个数据可以是响应式的对象、响应式的 ref、reactive 对象、函数等。父组件中使用 provide 提供数据后,所有的子组件都可以通过 inject 来访问这些数据。
- inject 是在子组件中使用的方法,用于接收父组件提供的数据。 它接收一个参数,即要注入的数据的键。在子组件中使用 inject 时,可以直接使用接收到的数据,而不需要在组件的配置选项中声明这些数据。
组件 Parent.vue:
<script setup>
// 引入 ref 函数,用于定义响应式数据
// 引入 provide,用于提供数据给所有子组件
import { ref, provide } from 'vue';
// 引入子组件1和子组件2
import Child1 from './Child1.vue';
import Child2 from './Child2.vue';
// 定义一个 message 响应式数据
const message = ref('我是父组件的数据')
// 使用 provide 将数据 message 提供给所有子组件
provide('message', message)
</script>
<template>
<div id="parent">
<Child1 />
<Child2 />
</div>
</template>
组件 Child1.vue:
<script setup>
import { inject } from 'vue';
// 使用 inject 获取来自父组件的数据 message
const parentMessage = inject('message');
</script>
<template>
<div id="child">
<p>子组件1: {{ parentMessage }}</p>
</div>
</template>
组件 Child2.vue:
<script setup>
import { inject } from 'vue';
// 使用 inject 获取来自父组件的数据 message
const parentMessage = inject('message');
</script>
<template>
<div id="child">
<p>子组件2: {{ parentMessage }}</p>
</div>
</template>
上面的代码中:
- 我们在父组件 Parent.vue 中定义了一个响应式数据 message
- 然后使用 provide 将数据 message 提供给所有子组件
- 在子组件 Child1 和 Child2 中,我们使用 inject 获取来自父组件的数据 message。
- 此时,在页面中,我们可以看到子组件获取到的父组件数据。
除了获取数据,我们同样也可以更改数据。
<script setup>
import { inject } from 'vue';
// 使用 inject 获取来自父组件的数据 message
const parentMessage = inject('message');
// 该方法用于更改父组件的message
const changeParentMessage = () => {
parentMessage.value = '我更改了message值'
}
</script>
<template>
<div id="child">
<p>子组件1: {{ parentMessage }}</p>
<button @click="changeParentMessage">更改父组件message</button>
</div>
</template>
上面这段代码中:
- 在子组件 Child1 中,我们定义了一个 changeParentMessage 函数,它更新了来自父组件的 message 值。
- 由于 message 在父组件中是响应式的,所以更新后该值将自动反映在父组件的视图中。
- 此时,我们点击一下按钮,子组件1和子组件2的值都会被更改。
总结:通过使用 provide 和 inject,数据能够在父组件和子组件之间进行传递和共享,实现了跨组件的通信。
5. v-model
v-model 可以同时支持多个数据双向绑定。
子组件 Child.vue:
<script setup>
const emit = defineEmits(['update:name', 'update:age']);
const changeParentMsg = () => {
emit('update:name', 'Steven')
emit('update:age', 36)
}
</script>
<template>
<div id="child">
<button @click="changeParentMsg">更新父组件中的name和age</button>
</div>
</template>
父组件 Parent.vue:
<script setup>
// 引入 ref 函数,用于定义响应式数据
import { ref } from 'vue';
// 引入子组件
import Child from './Child.vue';
// 定义两个响应式的变量
let name = ref('Echo');
let age = ref(26);
</script>
<template>
<div id="parent">
<p>父组件name: {{ name }}</p>
<p>父组件age: {{ age }}</p>
<!-- 使用 v-model 将父组件的 name 和 age 双向绑定到子组件的 name 和 age 上。 -->
<Child v-model:name="name" v-model:age="age"/>
</div>
</template>
上面的代码中:
- 我们在父组件内部使用 ref 函数定义了两个响应式变量 name 和 age,并给它们分别赋予初始值。然后在 template 中使用 v-model 将父组件的 name 和 age 双向绑定到子组件的 name 和 age 上。
- 在子组件内部,通过 defineEmits(['name', 'age']),我们定义了两个事件:update:name 和 update:age。这样,父组件可以监听并处理这两个事件。然后我们在 template 中定义了一个按钮,并在 script 中实现 changeParentMsg 的方法。当按钮被点击时,这个方法会调用 emit 方法来派发两个事件。
- 通过 emit('update:name', 'Steven'),我们触发了一个名为 update:name 的事件,并传递了一个参数 'Steven'。通过这个事件,我们可以告知父组件更新它的 name 值为 'Steven'。
- 通过 emit('update:age', 36),我们触发了一个名为 update:age 的事件,并传递了一个参数 36。通过这个事件,我们可以告知父组件更新它的 age 值为 36。
通过这样的设置,当父组件中的 name 或 age 发生变化时,它们会自动更新到子组件中。同时,当子组件中的 name 或 age 改变时,它们会通过 update:name 和 update:age 事件反馈给父组件,父组件会相应地更新自己的 name 和 age。这就实现了父子组件之间的双向绑定。
6. pinia
pinia 是一个为 vue3 设计的状态管理库,类似 Vuex 的设计模式,通过定义 store、状态、getter 和 action,来统一管理应用程序的状态和逻辑。
关于pinia的用法,可以看我之前发过的一篇文章,里面讲解的挺详细:
7. mitt
在 Vue 3 中,可以使用第三方库 mitt 实现组件之间的通信。mitt 是一个简单且强大的事件总线库(类似于 Vue 2 中的EventBus),它提供了一种方便的方式来在不同组件之间传递事件和数据。
下面我们以一个简单的示例来看下 mitt 是如何实现组件通信的:
首先,先安装 mitt.js
yarn add mitt
接着,创建一个 event bus:
// mitt/index.js
import mitt from 'mitt';
const bus = mitt();
export default bus;
在需要通信的组件中,导入 event bus 对象并进行事件的监听和触发:
组件 First.vue:
<script setup>
import mitt from '../mitt';
const emitEvent = () => {
mitt.emit('updateName', 36);
}
</script>
<template>
<div id="first">
<button @click="emitEvent">更新name和age</button>
</div>
</template>
组件 Second.vue:
<script setup>
import mitt from '../mitt';
import { ref } from 'vue';
let name = ref('Echo');
let age = ref(26);
mitt.on('updateName', (data) => {
name.value = 'Steven';
age.value = data;
});
</script>
<template>
<div id="second">
<p>name: {{ name }}</p>
<p>age: {{ age }}</p>
</div>
</template>
上面这个例子中:
- 我们创建了一个名为 mitt 的事件总线对象,并在两个组件中进行了引用。
- 在 First 组件中,当按钮被点击后,我们使用 mitt.emit 方法触发了一个自定义事件,并传递了一些数据。
- 在 Second 组件中,我们使用 mitt.on 方法监听了 updateName 事件,并在回调函数中接收到了传递的数据。然后我们将接收到的数据赋值给相应的属性,在模板中展示出来。
通过这种方式,我们可以在不同的组件中实现通信,First 组件可以通过事件总线发送事件和数据,Second 组件则监听事件并接收到数据进行处理。
以上就是我对于Vue3组件通信的一些理解,写的不好,欢迎大家指正!
转载自:https://juejin.cn/post/7280430881965080630