likes
comments
collection
share

一篇文章弄懂父子组件通信!

作者站长头像
站长
· 阅读数 23

引言

在Vue中,父子组件的通信是组件间交互的基础。而父子组件间的通信又有好几种情况。本文将通过几个示例详细讲解如何在父子组件之间进行通信。

父子通信

父组件传值,子组件接收

在 Vue 中,父组件可以通过属性(props)向子组件传递数据,子组件使用 defineProps 接收这些数据。示例如下。 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'
  
  const newMsg = ref('')
  const list = ref(['html', 'css'])
  
  const add = () => {
    list.value.push(newMsg.value)
  }
  </script>
  
  <style lang="css" scoped>
  
  </style>

child.vue

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

<script setup>
  const props =  defineProps({
        list:{
            type:Array,
            default: () => []

        }
    })
</script>

<style lang="scss" scoped>

</style>

一篇文章弄懂父子组件通信!

我们可以看到当在输入框输入后,点击按钮触发事件,传给子组件的数组也随着改变了。 这是最常用的一种父子通信方式,本质就是父组件将每次修改后的数组传给子组件,子组件再渲染出来。其中父组件通过 v-model 绑定输入框的值 parentMessage,并将其传递给子组件的 message 属性。 子组件使用 defineProps 接收 message 并进行渲染。

父组件修改子组件属性

父组件通过修改子组件的 props 来触发子组件的更新。示例如下:

parent.vue

<template>
    <div class="header">
      <input type="text" v-model="newMsg">
      <button @click="add">确定</button>
    </div>
  
    <Child :msg="val" />
  </template>
  
  <script setup>
  import { ref } from 'vue';
  import Child from './child.vue'
  
  const newMsg = ref('')
  const val = ref('')
  
  const add = () => {
        val.value = newMsg.value

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

child.vue

<template>
    <div class="body">
      <ul>
        <li v-for="item in list">{{item}}</li>
      </ul>
      <!-- <p>{{ msg }}</p> -->
    </div>
</template>

<script setup>
import {ref , computed, watch,onBeforeUpdate,onUpdated} from 'vue'
const list = ref(['html','css'])

const props =  defineProps({
    msg: {
      type: String,
      default: ''
    }

  })



    // watch(
    //     () =>props.msg,
    //     (newVal,oldVal) => {
    //       list.value.push(newVal)
    //     }
    // )

    onBeforeUpdate(() => {
        // console.log(props.msg); 在html用到的变量变更了就会执行
        list.value.push(props.msg)
    })

    // onUpdated(() => {
    //     console.log(props.msg);
    //     list.value.push(props.msg)
    //     // 会一直执行,因为子组件中的list更改,onUpdated会一直执行


    // })

 

</script>

<style lang="scss" scoped>

</style>

在这个示例中,我们在父组件的输入框绑定了 newMsg ,每当点击按钮触发了事件,就会同时去修改与子组件绑定的 msg 的值,当子组件的属性发生改变后触发响应,将修改后的值添加进要渲染的数组,达成效果。

而我们去实现发生改变有响应的事件,可以使用 watch去监听 或钩子函数。

  • watch 是 Vue 的一个响应式系统,它用于监听数据的变化,并在变化时执行特定的操作。 这里我们是用watch 正确追踪到 props.msg 的变化,再用回调函数,将新的 props.msg 值添加到 list 中,实现效果。

  • onBeforeUpdate 是在组件即将被更新之前调用的钩子函数,onUpdated 是在组件更新之后调用的钩子函数。 onBeforeUpdate 用于在组件即将更新时将 props.msg 的值推入 list 中。这是因为 onBeforeUpdate 会在任何响应式数据变化导致组件更新之前执行。

    而为什么不用 onUpdated 是因为onUpdated 钩子会在组件更新后触发,而在这个钩子中,我们又将 props.msg 推入 list,这会导致组件再次更新,触发 onUpdated 钩子,再次将 props.msg 推入 list,如此循环不止,陷入死循环。

    这便是用 onUpdated 陷入死循环

一篇文章弄懂父子组件通信!

如下便是使用 watchonBeforeUpdate 实现的效果

一篇文章弄懂父子组件通信!

provideinject 跨层级传递

provideinject 是 Vue 中用于跨层级组件传递数据的机制。但是只能允许祖先组件向后代组件传递数据,而且无需通过一层层的 props 传递。示例如下:

parent.vue

<template>
    <div class="header">
      <input type="text" v-model="newMsg">
      <button @click="add">确定</button>
    </div>
  
    <Child />
  </template>
  
  <script setup>
  import { ref, provide } from 'vue';
  import Child from './child.vue';
  
  const newMsg = ref('')
  const list = ref(['html', 'css'])
  
  const add = () => {
    list.value.push(newMsg.value)
  }

  provide('list', list.value)

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

child.vue

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

<script setup>
    import {inject} from 'vue';
    const list = inject('list');
</script>

<style lang="scss" scoped>

</style>

Parent.vue 中,我们使用 provide 提供了 list 数组,这个 list 可以被所有后代组件注入和使用。 在 Child.vue 中,我们使用 inject 方法接收父组件 Parent 提供的 list ,并渲染。

一篇文章弄懂父子组件通信!

子父通信

事件发布订阅机制

Vue中可以通过父子组件之间的事件订阅和发布,实现数据和事件的传递。示例如下:

parent.vue

<template>
    <Child @addMsg="handle" />
  
    <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 newMsg = ref('')
  const list = ref(['html', 'css'])
  const handle = (e) => {
    list.value.push(e)
  }
  
  </script> 
  
  <style lang="css" scoped>
  
  </style>

child.vue

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

<script setup>
import { ref,defineEmits } from 'vue';
    const newMsg = ref('')
    const emits = defineEmits(['addMsg'])  // 定义事件

    const add = () => {
        emits('addMsg',newMsg.value)  // 发布 ,顺序是先订阅后发布

    }
</script>

<style lang="scss" scoped>

</style>

defineEmits 用于声明子组件可以触发的事件。

在这个示例中,本质上是子组件传值给父组件中的 list ,再在父组件中渲染出来。

  • 子组件中 const emits = defineEmits(['addMsg']) 定义了一个名为 addMsg 的事件。在 add 方法中,通过 emits('addMsg', newMsg.value) 触发 addMsg 事件,并传递 newMsg.value 作为参数。 当用户点击按钮时,add 方法被调用,发布事件 addMsg,并将输入框中的数据传递给父组件。

  • 在父组件中,通过 @addMsg="handle" 监听子组件触发的 addMsg 事件。 当子组件触发 addMsg 事件时,父组件的 handle 方法被调用。handle 方法接收子组件传递的数据 msg。 在 handle 方法中,将接收到的数据添加到 list 中再进行。

使用 v-model 绑定

这个相当于是上文中的事件订阅发布机制的一种优化。v-model 在 Vue 中通常用于双向绑定数据。在这个例子中,它将父组件的 list 数据与子组件的 list prop 绑定在一起,简化了数据的传递和更新逻辑。

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>

child.vue

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

<script setup>
import { ref , defineProps,defineEmits} from 'vue'
const newMsg = ref('')
const emits = defineEmits(['update:list'])


const add = () => {
    // props.list.push(newMsg.value)
    // 不推荐
    const arr = props.list
    arr.push(newMsg.value)
    emits('update:list',arr)
}
const props = defineProps({
  list:[]
})

</script>

<style lang="scss" scoped>

</style>

在父组件中,使用 v-model:list="list" 将数据 list 绑定到子组件 Childlist prop

在子组件中,使用 defineEmits(['update:list']) 来定义组件可以触发的事件,并通过 emits('update:list', newMsg.value) 触发事件,并将 newMsg.value 作为参数传递给父组件,父组件的 list 会被更新,从而达成效果。

可以看到与事件订阅发布相比, parent.vue 简化了许多,方法都不需要了,只需要绑定就好了。

注意事项 不推荐直接修改 props 的原因是Vue 强调单向数据流,即数据应该从父组件流向子组件。props 是用于父子组件通信的一种机制,设计上是为了让父组件将数据传递给子组件,而子组件则负责接收这些数据并进行展示或处理。直接修改 props 违反了这种单向数据流的原则,因为它使得子组件可以修改由父组件传递来的数据,导致数据流动变得不明确和难以追踪。

父组件通过 ref 获取子组件实例

父组件可以通过 ref 获取子组件的实例,从而访问子组件暴露出来的数据和方法。此方式比较简单,直接在子组件中修改 list ,然后再暴露传递给父组件,父组件获取后再渲染。示例代码如下:

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 } from 'vue';
import Child from './child.vue'
    import { ref } from 'vue'

    const childRef = ref(null)

    onMounted(() => {
        console.log(childRef.value.list)
    })

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

child.vue

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

<script setup>
import { ref , defineExpose} from 'vue';
  
  const newMsg = ref('')
  const list = ref(['html', 'css'])
  
  const add = () => {
    list.value.push(newMsg.value)
  }
  defineExpose({ list })    
//   子组件心甘情愿暴露出来list

</script>

<style lang="scss" scoped>

</style>
  • 子组件使用 defineExpose 暴露 message 数据。

  • 父组件通过 ref 获取子组件实例,并在 showMessage 方法中访问子组件的 message 数据

总结

通过以上示例,我们可以看到在 Vue 中父子组件通信的多种方式,每种方式都有特定的使用场景:

  • propsdefineProps:用于父组件向子组件传递数据。
  • 修改子组件属性:父组件通过修改 props 来触发子组件更新。
  • inject:用于祖先组件向后代组件传递数据。
  • 事件机制:子组件通过事件向父组件传递数据。
  • v-modeldefineEmits:用于父组件与子组件之间的双向数据绑定。
  • refdefineExpose:用于父组件访问子组件的实例和数据。

理解和掌握这些通信方式,可以帮助我们在开发中更好地组织和管理 Vue 组件,如果觉得这篇文章对你有用,可以点个赞哦。

转载自:https://juejin.cn/post/7394722009430245426
评论
请登录