Vue.js 组件通讯秘籍:轻松掌握 Props 、 emit 和 Refs技巧
引言
在现代前端开发中,Vue.js 以其简洁易用的特性和强大的功能广受开发者喜爱。作为一款渐进式的 JavaScript 框架,Vue.js 既可以用于构建小型项目中的单个页面组件,也能够在大型复杂项目中发挥强大的作用。而在实际开发中,我们常常需要处理父组件与子组件之间的数据传递和事件通讯。
父子组件通讯
父子组件通讯是 Vue.js 中非常重要的一部分,它不仅影响组件之间的数据流动,还直接关系到应用的响应式和可维护性。掌握父子组件通讯的各种方式,不仅能够让你在开发过程中更加游刃有余,还能帮助你构建更加清晰和高效的组件结构。
那么今天,呆同学将和大家一起来了解学习父组件向子组件通讯的一种方式——通过 props
传递数据,以及子组件向父组件通讯的三种方式——通过 $emit
触发事件、通过v-model
、通过 refs
直接访问子组件实例。每种方式我都会结合具体的代码示例进行讲解,帮助你全面理解并灵活运用这些通讯方式。希望通过这篇文章,能够为你在 Vue.js 开发中的组件通讯提供一些有用的参考。
在接下来的学习过程中,我们都是以下面这个demo为主要实例,我们想要让input框中的数据同下面的列表的数据通过父子组件通讯流通起来。
父向子组件通讯
在Vue.js中,父组件可以通过props
向子组件传递数据,这是父子组件通讯中最常见的一种方式。通过这种方式,父组件可以将数据作为属性传递给子组件,子组件可以通过props
接收这些数据并进行处理。例如:
父组件App.vue
<template>
<div>
<div class="input-group">
<input type="text" v-model="value">
<button @click="add">添加</button>
</div>
<Child :msg="toChild"></Child>
</div>
</template>
<script setup>
import Child from '@/components/Child.vue';
import { ref } from 'vue';
const value = ref('');
let toChild = ref('');
const add = () => {
toChild.value = value.value;
value.value = '';
}
</script>
<style lang="css" scoped>
</style>
在这个父组件中,我们通过v-model
双向绑定输入框的内容,通过点击按钮来触发add
方法。add
方法将输入框的内容赋值给toChild
,然后清空输入框。这里使用toChild
来传递数据给子组件Child
。
子组件Child.vue
子组件通过props
接收父组件传递的数据,并进行相应的处理和显示。
<template>
<div>
<div class="child" v-for="item in list">
<ul>
<li>{{ item }}</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
const list = ref(['html', 'css', 'js']);
const props = defineProps({
msg: {
type: String,
default: ''
}
})
watch(
() => props.msg,
(newVal, oldVal) => {
if (newVal) {
list.value.push(newVal);
}
}
)
</script>
<style lang="css" scoped>
</style>
在子组件中,我们使用defineProps
定义了一个msg
属性,用于接收父组件传递的数据。通过watch
监听msg
属性的变化,每当msg
属性发生变化时,便将新值添加到list
中。这样,父组件传递的数据便会动态地显示在子组件中。
补充:
watch
是 Vue.js 中用于监听数据变化并执行相应操作的一个工具。它可以监听一个或多个响应式数据,当这些数据发生变化时,会执行特定的回调函数。watch
通常用于需要在数据变化时进行一些复杂的逻辑处理,而不仅仅是简单的模板更新。使用场景
- 监听单个数据变化:当某个数据发生变化时,执行特定逻辑。
- 监听多个数据变化:可以同时监听多个数据,并在任意一个数据变化时执行逻辑。
- 监听复杂数据结构:如对象或数组的深层变化。
子向父组件通讯
方式一:通过 $emit
触发事件
在 Vue.js 中,子组件可以通过 $emit
触发事件向父组件传递数据。这种方式非常适用于子组件需要通知父组件某些操作或状态变化的场景。
父组件App2.vue
在父组件中,我们通过事件绑定 (@add1
) 来监听子组件触发的事件,并在事件处理函数中执行相应的逻辑。
<template>
<!-- 订阅add1事件 -->
<Child @add1="handle"></Child>
<div class="child">
<ul>
<li v-for="item in list" :key="item">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import { ref } from "vue";
import Child from '@/components/child2.vue';
const list = ref(['html', 'css', 'js']);
const handle = (val) => {
list.value.push(val);
};
</script>
<style lang="scss" scoped></style>
在这个父组件中,我们通过 @add1="handle"
订阅了子组件触发的 add1
事件,并定义了 handle
方法来处理这个事件。每当子组件触发 add1
事件时,handle
方法会将传递过来的值添加到 list
中。
子组件child2.vue
在子组件中,我们通过 defineEmits
定义事件,并在需要的时候通过 emit
触发事件,将数据传递给父组件
<template>
<div>
<div class="input-group">
<input v-model="value" type="text">
<button @click="add">添加</button>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
const value = ref('');
const emit = defineEmits(['add1']); // 创建一个add1事件
const add = () => {
// 将value给到父组件
emit('add1', value.value); // 发布事件
value.value = ''; // 清空输入框
};
</script>
<style lang="css" scoped></style>
在子组件中,我们首先定义了 add1
事件,并在 add
方法中通过 emit
触发该事件,并将输入框的值作为参数传递给父组件。这样,每当用户点击“添加”按钮时,子组件会触发 add1
事件,并将当前输入框的值传递给父组件。
子组件通过 $emit
触发自定义事件 add1
,父组件通过监听该事件来接收数据并执行相应的处理逻辑。
方式二:通过 v-model
实现双向绑定
在 Vue.js 中,可以使用 v-model
实现父子组件之间的数据双向绑定。通过这种方式,子组件可以修改传递进来的数据并自动同步到父组件。
父组件App3.vue
在父组件中,我们使用 v-model:list
来绑定子组件的 list
属性,从而实现双向数据绑定。
<template>
<Child v-model:list="list"></Child>
<div class="child">
<ul>
<li v-for="item in list" :key="item">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import { ref } from "vue";
import Child from '@/components/child3.vue';
const list = ref(['html', 'css', 'js']);
</script>
<style lang="scss" scoped></style>
在这个父组件中,我们通过 v-model:list="list"
将 list
属性绑定到子组件 Child
,从而实现父组件和子组件之间的数据双向绑定。
子组件child3.vue
在子组件中,我们通过 defineProps
接收 list
属性,并通过 defineEmits
定义 update:list
事件,实现数据的更新和传递。
<template>
<div class="input-group">
<input type="text" v-model="value">
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref, defineProps, defineEmits } from "vue";
const value = ref('');
const props = defineProps({
list: {
type: Array,
default: () => []
}
});
const emit = defineEmits(['update:list']);
const add = () => {
const arr = props.list.slice(); // 拷贝数组以避免直接修改父组件的数据
arr.push(value.value);
emit('update:list', arr);
value.value = ''; // 清空输入框
};
</script>
<style lang="scss" scoped></style>
在子组件中,我们通过 defineProps
接收父组件传递的 list
属性,并通过 defineEmits
定义 update:list
事件。当用户点击“添加”按钮时,add
方法会拷贝 props.list
,将新的值添加到拷贝数组中,然后通过 emit
触发 update:list
事件,将更新后的数组传递给父组件。
补充:
在 Vue 3 中使用
defineEmits
定义事件时,事件名并不一定要以update:
开头。update:
前缀通常用于表示一个特定的更新事件,例如在使用v-model
时,Vue 内置的指令会自动触发带有update:
前缀的事件来更新绑定的数据。但是,当你自定义事件时,可以根据你的需求任意定义事件名,不一定需要遵循
update:
的格式。例如:const emit = defineEmits(['addItem']);
然后在子组件中触发这个事件:emit('addItem', newItem);
在父组件中监听这个事件:<Child @addItem="handleAddItem" />
总之,
defineEmits
允许你自由定义事件名称,无需严格遵循update:
前缀的规范,这取决于你的设计和使用场景。
方式三:通过 refs
访问子组件实例
在 Vue.js 中,父组件可以使用 refs
直接访问子组件实例,从而读取和操作子组件中的数据或方法。这种方式适用于需要直接访问子组件内部状态或方法的场景。
父组件App4.vue
在父组件中,我们通过 ref
获取子组件实例,并通过子组件暴露的方法或数据进行操作。
<template>
<Child ref="childRef"></Child>
<div class="child">
<ul>
//childRef?.list表示只有在在子组件已经挂载完成后才能访问子组件暴露的list数据
<li v-for="item in childRef?.list" :key="item">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import Child from '@/components/child4.vue';
const childRef = ref(null);
onMounted(() => {
console.log(childRef.value);
});
</script>
<style lang="scss" scoped></style>
在这个父组件中,我们使用 ref
获取子组件的实例,通过 childRef
引用来访问子组件暴露的数据 list
。在模板中,我们使用 v-for
循环显示 childRef.list
中的内容。childRef?.list
表示只有在在子组件已经挂载完成后才能访问子组件暴露的list
数据
子组件child4.vue
在子组件中,我们通过 defineExpose
暴露需要被父组件访问的数据或方法。
<template>
<div class="input-group">
<input type="text" v-model="value">
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref, defineExpose } from "vue";
const value = ref('');
const list = ref(['html', 'css', 'js']);
const add = () => {
list.value.push(value.value);
value.value = ''; // 清空输入框
}
defineExpose({ list }); // 暴露数据
</script>
<style lang="scss" scoped></style>
在子组件中,我们定义了 value
和 list
两个响应式变量,并在 add
方法中将输入框的值添加到 list
中。通过 defineExpose
暴露 list
,使父组件能够直接访问和使用。
这种方法子组件只需要暴露数据给父组件,然后父组件引用子组件暴露的数据,但是需要注意生命周期,因为需要在子组件挂载完成后父组件才能成功获取子组件的数据。
总结
在 Vue.js 中,我们通过四种不同的方式实现了父子组件之间的通讯。每种方式都有其特定的应用场景和优点,从简单的数据传递到复杂的双向绑定和直接实例访问,为我们提供了灵活而强大的工具来构建清晰、高效的组件架构。选择合适的通讯方式,能够有效提升开发效率,同时保持代码的可维护性和可读性。
转载自:https://juejin.cn/post/7391034124374327311