父子间的悄悄话:Vue.js中的五种通信方式

在编程的世界里,组件之间的沟通就像家庭成员之间的交流一样重要。 当你开始构建一个Vue.js应用时,你很快就会发现,不同组件之间需要相互通信才能协同工作。今天,我们将一起探索Vue.js中组件间的通信技巧。

接下来,我将通过一个简单的todos demo来详细介绍五种父子间的通信方式。如果你对Vue的父子通信有点陌生,那么让我们一起开始这段旅程吧!

首先是最简单的 props传值,

1. 当爹的总是要给儿子点东西,对吧? —— 用props给子组件传递数据的艺术。

在Vue中,数据传递就像家庭中的传统:父亲总是会给儿子一些东西。这就是我们所说的props,它是Vue组件的一个自定义属性,用于接收从父组件传递过来的数据。

Props(属性)

props是Vue组件的一个自定义属性,用于接收从父组件传递过来的数据。在子组件中定义props,可以使得父组件的数据能够在子组件中使用。

如何使用Props进行父子通信

步骤 1: 在子组件中定义Props

在子组件中定义需要接收的props。这可以通过在组件的<script setup>部分使用defineProps来完成。

// Child.vue
<template>
  <div class="body">
    <ul>
      <li v-for="item in list">{{ item }}</li>
    </ul>
  </div>
</template>

<script setup>
import { defineProps } from 'vue'

// 定义一个名为 list 的 prop,类型为 Array,默认值为空数组
const props = defineProps({
  list: {
    type: Array,
    default: () => []
  }
})
</script>

<style lang="css" scoped>
</style>

在这个例子中,我们定义了一个名为listprop,它的类型是Array,并且有一个默认值是一个空数组。这意味着即使父组件没有传递任何数据,子组件仍然可以正常工作,因为它已经有了一个默认的列表。

步骤 2: 在父组件中传递数据

在父组件的模板中,将数据绑定到子组件的props上。这通常通过在子组件的标签上使用冒号(:)来表示属性绑定。

// Parent.vue
<template>
  <div class="header">
    <input type="text" v-model="newMsg">
    <button @click="add">确定</button>
  </div>
  <Child :list="list" />
</template>

<script setup>
import { ref } from 'vue'
import Child from './child.vue'

// 创建一个可响应式变量 newMsg,用于存储新的消息
const newMsg = ref('')
// 创建一个可响应式的数组 list,用于存储所有消息
const list = ref(['html', 'css'])

// 添加新消息到 list 数组中
const add = () => {
  list.value.push(newMsg.value)
  newMsg.value = ''
}
</script>

<style lang="css" scoped>
</style>

在父组件中,我们创建了一个名为newMsg的响应式变量,用于存储用户输入的新消息。同时,我们还创建了一个名为list的响应式数组,用于存储所有的消息。当用户点击“确定”按钮时,add函数会被调用,将newMsg的值添加到list数组中,并清空newMsg

接着,我们使用<Child :list="list" />这样的形式将list数组传递给子组件Child。冒号(:)表示我们正在绑定一个属性,这里的属性名是list,而它的值是父组件中的list变量。

注意:

  1. 验证 Props:
    除了定义typedefault之外,你还可以为props定义更多的验证规则。例如,你可以定义一个最小长度、最大长度等。

    const props = defineProps({
      list: {
        type: Array,
        required: true, // 必须传递
        validator: (value) => value.length >= 3 // 验证列表长度至少为3
      }
    })
    
  2. Props 的动态绑定:
    你可以使用动态属性绑定的方式将props绑定到子组件上。这种方式特别适用于需要传递多个props的情况。

    <Child v-bind="{ ...propsObject }" />
    
  3. Props 的解构:
    如果子组件中有多个props,你可以使用解构赋值的方式简化代码。

    const { list } = defineProps({
      list: {
        type: Array,
        default: () => []
      }
    })
    
  4. Props 的注意事项:

    • 不要在子组件中直接修改props的值。
    • 如果需要根据props的值做出改变,可以使用计算属性或方法。
    • 使用props时,确保遵循Vue的命名约定,使用小驼峰式命名法。

相信大家在写项目的时候都遇到过类似场景,

“应用中有多个组件,包括一个头部组件(Header),用于添加新的待办事项;一个主体组件(Body),用于显示待办事项列表;还有一个底部组件(Footer),用于显示待办事项的状态统计信息。头部组件需要将新添加的待办事项通知给主体组件,以便实时更新待办事项列表。此外,主体组件还需要将待办事项列表的变化通知给底部组件,以便实时更新状态统计信息。”。

这时就需要子组件更改父组件传来的数据了,不知道大家知不知道其实在vue3中 子组件是可以直接改父组件传来的数据的,而且父组件也是可以感应到的。但是官方不推荐直接在子组件中更改父组件传来的数据,因为这样违背了尤雨溪的设计理念——单向数据流,这时候就需要发布订阅的机制了。

2. 儿子长大了,也要学会跟爹汇报工作了! —— 通过发布订阅机制让子组件向父组件传递信息。

在Vue.js中,子组件可以向父组件传递信息,就像儿子向父亲汇报工作一样。这就是发布订阅机制,子组件通过$emit触发事件,父组件通过v-on或@监听这些事件。

发布订阅机制的基本概念

在Vue中,组件实例提供了一个自定义事件系统,允许组件实例触发和监听事件。这类似于发布订阅模式,其中:

  • 发布:子组件通过$emit方法触发一个事件,并可以传递数据。
  • 订阅:父组件通过v-on@指令监听子组件触发的事件。

如何使用发布订阅机制进行子父通信

步骤 1: 子组件发布事件

在子组件中,当你需要向父组件传递信息时,可以使用emit方法来触发一个事件。你可以给事件命名,并且可以传递数据作为参数。

// Child.vue
<template>
  <div class="header">
    <input type="text" v-model="newMsg">
    <button @click="add">确定</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { defineEmits } from 'vue'

const newMsg = ref('')
const emits = defineEmits(['addMsg'])

const add = () => {
  emits('addMsg', newMsg.value)
  newMsg.value = ''
}
</script>

<style lang="css" scoped>
</style>

在这个子组件中,我们首先导入了refdefineEmits。然后定义了一个响应式的变量newMsg用于存储新的消息,以及一个名为emits的对象,用于触发事件。

当用户点击“确定”按钮时,add函数会被调用。在这个函数中,我们使用emits('addMsg', newMsg.value)来触发一个名为addMsg的事件,并将newMsg的值作为参数传递给这个事件。最后,我们将newMsg的值清空,以便用户可以继续输入新的消息。

步骤 2: 父组件订阅事件

在父组件中,你可以使用v-on@指令来监听子组件触发的事件。当事件被触发时,可以执行一个方法来处理传递过来的数据。

// Parent.vue
<template>
  <Child @addMsg="add" />
  <div class="body">
    <ul>
      <li v-for="item in list">{{ item }}</li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Child from './child.vue'

const list = ref(['html', 'css'])

const add = (msg) => {
  list.value.push(msg)
}
</script>

<style lang="css" scoped>
</style>

在父组件中,我们首先导入了ref和子组件Child。接着,我们创建了一个响应式的数组list,用于存储所有的消息。

当子组件触发addMsg事件时,父组件中的add函数会被调用,并且该函数接收一个参数msg,这是子组件通过$emit传递过来的数据。在这个函数中,我们将msg添加到list数组中,这样就可以在父组件中显示新的消息了。

注意:

  1. 事件名称:

    • 子组件触发的事件名称必须是唯一的,以避免与内置事件或其它自定义事件冲突。
  2. 传递多个参数:

    • 你可以在$emit方法中传递多个参数,只需要在括号内列出即可。
    emits('addMsg', newMsg.value, someOtherData)
    
  3. 动态事件名:

    • 你还可以使用动态事件名,这对于需要动态绑定事件名的场景非常有用。
    <Child :@event-name="add" />
    
  4. 事件修饰符:

    • Vue提供了事件修饰符,如.stop.prevent等,可以用来控制事件的行为。
    <Child @addMsg.stop="add" />
    
  5. 事件监听器的注意事项:

    • 如果事件监听器中包含多个方法,这些方法将会按照它们被声明的顺序依次调用。
    • 你可以使用$off方法来移除事件监听器。
  6. 事件的命名空间:

    • 对于复杂的事件结构,你可以使用命名空间来组织事件,避免命名冲突。
    emits('namespace:addMsg', newMsg.value)
    

3. 双向绑定,让父子沟通无障碍! —— 使用v-model实现子父间的双向数据绑定。

当儿子长大了,他们也需要学会如何跟爹进行双向沟通。在Vue.js中,这就是通过v-model实现的。
在Vue.js中,v-model 是一个指令,用于在表单输入和应用状态之间创建双向数据绑定。除了在单个组件内部使用外,v-model 也可以用于父子组件之间的通信,实现子组件向父组件传递数据的功能。

对于需要实现双向数据绑定的情况,v-model提供了一种简洁的方式来处理子父间的通信。

V-Model 在组件中的工作原理

在组件中使用 v-model 时,Vue 会做以下事情:

  1. v-model 的值传递给子组件的 props,默认情况下是名为 modelValue 的 prop。
  2. 当子组件内部发生数据变化时,通过 $emit 触发一个事件,默认情况下是 update:modelValue 事件,并将新的值作为参数传递给父组件。

如何使用 V-Model 实现子父通信

步骤 1: 子组件接收 modelValue Prop

在子组件中,你需要定义一个 props 来接收 v-model 传递的值。此外,你还需要在子组件中定义一个方法来处理数据的变化,并且需要使用defineExpose来暴露子组件的list属性。

// Child.vue
<template>
  <div class="header">
    <input type="text" v-model="newMsg">
    <button @click="add">确定</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const newMsg = ref('')
const props = defineProps({
  list: {
    type: Array,
    default: () => []
  }
})

const emit = defineEmits(['update:modelValue'])

const add = () => {
  props.list.push(newMsg.value)
  newMsg.value = ''
  emit('update:modelValue', props.modelValue)
}

defineExpose({ list: props.list }) // 子组件心甘情愿暴露出来
</script>

<style lang="css" scoped>
</style>

在这个子组件中,我们首先定义了一个props对象,其中包含了list prop,它的类型是Array,并且有一个默认值是一个空数组。这样,当父组件使用v-model绑定子组件时,子组件就能接收到这个数组。

接下来,我们定义了一个emit对象,用于触发事件。当用户点击“确定”按钮时,add函数会被调用。在这个函数中,我们首先将newMsg的值添加到list数组中,然后清空newMsg的值。最后,我们通过emit('update:list', props.list)来触发一个update:list事件,并将更新后的数组作为参数传递出去。

步骤 2: 父组件使用 v-model 绑定

在父组件的模板中,你可以使用 v-model 指令绑定子组件,这样当子组件触发 update:list 事件时,父组件的绑定数据会自动更新。或者使用@update:list='list'

// Parent.vue
<template>
  <Child v-model:list="list" />
  <div class="body">
    <ul>
      <li v-for="item in list">{{ item }}</li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Child from './child.vue'

const list = ref(['html', 'css'])
</script>

<style lang="css" scoped>
</style>

在父组件中,我们首先定义了一个响应式的数组list,用于存储所有的消息。接着,我们使用<Child v-model:list="list" />这样的形式将list数组传递给子组件Childv-model指令告诉Vue使用list作为modelValue prop的值,并且当子组件触发update:list事件时,父组件中的list也会相应地更新。

拓展:

  1. 自定义模型选项:

    • 你可以通过在子组件中定义model选项来自定义v-model使用的prop和事件名称。
    defineProps({
      customModelValue: {
        type: Array,
        default: () => []
      },
      model: {
        prop: 'customModelValue',
        event: 'update:customModelValue'
      }
    })
    
  2. 动态绑定:

    • 你可以使用动态绑定的方式来自定义v-model绑定的prop和事件名称。
    <Child v-model:customModelValue="list" />
    
  3. 多值绑定:

    • 有时候,你可能需要使用v-model来绑定多个值。在这种情况下,你可以使用v-model的语法糖。
    <Child v-model:value1="value1" v-model:value2="value2" />
    
  4. 非标准元素:

    • v-model不仅限于表单元素,还可以用于自定义组件和其他元素,如<select><textarea>等。

4. 想跟儿子说话?直接喊他名字就好! —— 通过ref实现子父通信的小技巧。

有时候,当爹的想要跟儿子说话,直接喊他的名字就可以了。在Vue.js中,这就是通过ref实现的子父通信。

使用 Ref 进行子父通信的基本概念

ref是一个特殊的属性,当用在Vue组件上时,它允许你直接访问该组件的实例。通过这种方式,父组件可以调用子组件的方法,或者访问和修改子组件的数据。

如何使用 Ref 实现子父通信

步骤 1: 在父组件中为子组件定义 Ref

在父组件的模板中,你需要在子组件上添加一个ref属性,并为它指定一个唯一的名字。

// Parent.vue
<template>
  <Child ref="childRef" />
  <div class="body">
    <ul>
      <li v-for="item in childRef?.list">{{ item }}</li>
    </ul>
  </div>
</template>

<script setup>
import { onMounted, ref } from 'vue'
import Child from './child.vue'

const childRef = ref(null)

onMounted(() => {
  console.log('Child component list:', childRef.value?.list)
})
</script>

<style lang="css" scoped>
</style>

在这个父组件中,我们首先定义了一个ref变量childRef,用于存储子组件的引用。接着,我们在子组件Child上使用了ref="childRef",这样Vue就会在组件实例化后将其存储在childRef中。

onMounted钩子会在组件挂载完成后被调用,我们可以在这里安全地访问childRef的值。在这个例子中,我们只是简单地打印了子组件的list属性,以展示如何访问子组件的属性。

步骤 2: 在子组件中暴露属性或方法

在子组件中,你需要使用defineExpose来暴露属性或方法,以便父组件可以通过ref访问它们。

// Child.vue
<template>
  <div class="header">
    <input type="text" v-model="newMsg">
    <button @click="add">确定</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const newMsg = ref('')
const list = ref(['html', 'css'])

const add = () => {
  list.value.push(newMsg.value)
  newMsg.value = ''
}

defineExpose({ list })
</script>

<style lang="css" scoped>
</style>

在这个子组件中,我们定义了一个响应式的数组list,用于存储所有的消息。我们还定义了一个add函数,用于将新的消息添加到list数组中。

最后,我们使用defineExpose({ list })来暴露list属性,这样父组件就可以通过ref来访问它。

注意

  • ref只在组件渲染完成后才填充,并且它是非响应式的。因此,你应当避免在模板或计算属性中直接引用ref
  • 如果你在子组件上使用v-forref将是一个包含所有子组件实例的数组。
  • 使用ref进行子父通信可能会使组件之间的耦合度增加,因此应当谨慎使用,并确保不会破坏组件的独立性。

拓展

  1. 调用子组件的方法:

    • 你还可以通过ref调用子组件的方法。只需确保子组件中使用defineExpose暴露了该方法。
    // Child.vue
    defineExpose({ add })
    
    // Parent.vue
    childRef.value.add()
    
  2. 动态ref

    • 你可以在ref属性中使用表达式,这样可以根据不同的条件动态地选择子组件。
    <Child ref="childRefs[currentTab]" />
    
  3. 多层级组件:

    • 如果你有多个层级的组件,你可以通过链式调用来访问更深层次的组件。
    grandParentRef.value.childRef.value.grandChildRef.value
    

当我们需要跨越多个组件层级传递数据时,使用上面的链式调用就显得不是那么优雅了,那么provide和inject就成为了解决这一问题的有效工具。

5. 家族传承,从爷爷到孙子,一代代往下传! —— 利用provide和inject实现跨层级组件通信的妙招。

provide和inject就像是家族中的传统,一代代往下传。
provideinject 是Vue.js提供的一种依赖注入方式,它允许一个祖先组件向其所有子孙组件提供数据或方法,而不需要通过逐层传递props。这种机制特别适合于跨多个组件层级共享数据的情况。

Provide/Inject的基本概念

provideinject 是Vue.js提供的一种依赖注入方式,它允许一个祖先组件向其所有子孙组件提供数据或方法,而不需要通过逐层传递props

Provide

provide 选项允许你定义一个可以被后代组件注入的数据或方法。你可以在组件的provide选项中提供一个对象,或者在组件的setup函数中使用provide函数。

Inject

inject 选项用于接收祖先组件通过provide提供的值。你可以在组件的inject选项中指定需要接收的数据或方法,或者在组件的setup函数中使用inject函数。

如何使用Provide/Inject

步骤 1: 在祖先组件中提供数据或方法

在祖先组件中,你需要使用provide来定义要提供给子孙组件的数据或方法。

// Parent.vue
<template>
  <Child />
  <div class="body">
    <ul>
      <li v-for="item in list">{{ item }}</li>
    </ul>
  </div>
</template>

<script setup>
import { ref, provide } from 'vue'
import Child from './child.vue'

const list = ref(['html', 'css'])

provide('list', list)
</script>

<style lang="css" scoped>
</style>

在这个父组件中,我们定义了一个响应式的数组list,用于存储所有的消息。接着,我们使用provide('list', list)来提供这个数组,这样子孙组件就可以通过inject来获取它。

步骤 2: 在子孙组件中注入数据或方法

在需要使用这些数据或方法的子孙组件中,使用inject来接收这些值。

// Child.vue
<template>
  <div class="header">
    <input type="text" v-model="newMsg">
    <button @click="add">确定</button>
  </div>
</template>

<script setup>
import { ref, inject } from 'vue'

const newMsg = ref('')
const list = inject('list')

const add = () => {
  list.value.push(newMsg.value)
  newMsg.value = ''
}
</script>

<style lang="css" scoped>
</style>

在这个子组件中,我们使用inject('list')来接收从父组件提供的list数组。接着,我们定义了一个add函数,用于将新的消息添加到list数组中。

注意事项

  • provideinject的关系是基于组件的层级结构的,只有当组件在提供者的后代组件中时,它才能注入所提供的数据或方法。
  • 提供的数据如果是响应式的(例如使用refreactive),那么注入它的组件将能够响应这些数据的变化。
  • provideinject的绑定不是响应式的。如果你传入了一个响应式的ref值,那么它是作为ref对象传入的,而不是作为它的内部值。
  • 使用provideinject时,应当注意不要过度使用,以免造成组件之间的过度耦合。

拓展

  1. 跨组件层级通信:

    • provideinject非常适合跨组件层级通信,特别是当你需要将数据传递给多个层级下的组件时。
  2. 默认值:

    • 你可以在inject时提供一个默认值,以防万一祖先组件没有提供相应的值。
    const list = inject('list', ['default'])
    
  3. 命名空间:

    • 如果你有多个祖先组件都提供了相同名称的数据,你可以使用命名空间来区分它们。
    provide('namespace:list', list)
    const list = inject('namespace:list')
    

总结

Vue提供了多种方式来实现组件之间的数据传递和状态管理,包括父子通信、子父通信、v-model双向绑定和provide/inject跨组件通信。以下是我对这五种通信方式的总结

  1. 父子通信 - Props:父组件通过props向子组件传递数据,子组件通过defineProps接收这些数据。这是一种单向的数据传递方式,通常用于简单的数据传递。
  2. 子父通信 - 发布订阅机制:子组件通过$emit触发事件,父组件通过v-on或@监听这些事件。这种方式更灵活,适用于需要子组件向父组件传递信息的情况。
  3. 子父通信 - V-Model:v-model创建了表单输入和应用状态之间的双向数据绑定。在父子组件之间使用v-model可以实现子组件向父组件传递数据的功能。
  4. 子父通信 - 使用 Ref:父组件通过ref直接访问子组件的实例和它的属性或方法。这种方式可以实现更直接的子父组件通信,但可能会增加组件之间的耦合度。
  5. Provide/Inject:祖先组件通过provide提供数据或方法,子孙组件通过inject接收这些值。这种方式特别适合于跨多个组件层级共享数据的情况。
  • 10
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值