Vue 父子组件通信

176 阅读4分钟

父子通信

Vue 中最直接的通信方式之一是父子通信,其中父组件向子组件传递数据,而子组件可以接收这些数据并在其模板中使用它们。这种通信模式使用起来非常直观,只需要在父组件中使用 <Child :prop-name="value"/> 的形式即可将数据传递给子组件。在子组件中,我们使用 defineProps 来声明接收的属性。

image.png

definePropsprops

  • 父组件通过props将数据传递给子组件。
  • 子组件通过defineProps来接收父组件传递的props。

父组件(输入框):

<template>
    <div>
        <div class="header">
            <input type="text" v-model="newMsg">
            <button @click="add">确定</button>
        </div>
        <Child :list="list"/>
    </div>
</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>

子组件(list列表):

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

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

<style lang="css" scoped>

</style>

onBeforeUpdate

父组件(输入框):

<template>
    <div>
        <div class="header">
            <input type="text" v-model="newMsg">
            <button @click="add">确定</button>
        </div>
        <Child :msg="val"/>
    </div>
</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>

子组件(list列表)用生命周期函数onBeforeUpdate()更新父组件的列表数据:

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

<script setup>
import { computed, ref, watch, onBeforeUpdate, onUpdated } from 'vue'

const list = ref([])

const props = defineProps({
   msg: ''
})

 onBeforeUpdate(() =>{
     list.value.push(props.msg)
 })

</script>

<style lang="css" scoped>

</style>

响应式watch

父组件(输入框):

<template>
    <div>
        <div class="header">
            <input type="text" v-model="newMsg">
            <button @click="add">确定</button>
        </div>
        <Child :msg="val"/>
    </div>
</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>

子组件(list列表)用watch监听父组件更新的列表数据:

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

<script setup>
import { computed, ref, watch, onBeforeUpdate, onUpdated } from 'vue'

const list = ref([])

const props = defineProps({
   msg: ''
})

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

</script>

<style lang="css" scoped>

</style>

子父通信

子组件向父组件传递数据则稍微复杂一些,但同样有几种有效的方式。

一种常见的方式是使用自定义事件。子组件可以触发一个事件,并附带需要传递的数据。父组件可以在模板中监听这个事件,并在事件处理器中接收数据。即发布订阅模式

另一种方式是使用 v-model 指令。父组件v-model绑定属性传给子组件,子组件发布update:xxx 事件通知父组件数据更新了。

此外,父组件还可以通过ref获取到子组件的dom结构,从而获取到子组件defineExpose()暴露出来的结构。

发布订阅模式

父组件(列表接收子组件传递的值):

<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 list = ref([])

const val = ref('')

const handle = (e) =>{
    list.value.push(e)
}
</script>

<style lang="css" scoped>

</style>

通过Child @addMsg="handle"引入Child子组件,并且监听addMsg事件,当事件被触发时则调用handle方法。handle通过参数e得到子组件传递的数据,并将其添加到list里。

子组件(输入框 传递数据给父组件):

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

<script setup>
import { computed, ref, watch, onBeforeUpdate, onUpdated } from 'vue'
const newMsg = ref('')

const emits = defineEmits(['addMsg'])   // 定义
const add = () =>{
    emits('addMsg',newMsg.value)  // 发布
}
</script>

<style lang="css" scoped>

</style>

使用defineEmits来定义子组件可以触发的自定义事件(addMsg)。当addMsg事件触发时,会通过调用emits('addMsg',newMsg.value)newMsg.value作为参数传递给父组件接收。

v-model

父组件(列表接收子组件传递的值):

<template>
    <Child v-model:list="list" @update:list="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 list = ref(['HTML', 'CSS'])

const handle = (e) =>{
    list.value = e
}
</script>

<style lang="css" scoped>

</style>

父组件用v-model:list="list"实现双向绑定属性list传递给子组件,让父子组件数据一起更新。

子组件(输入框 传递数据给父组件):

<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: []
})
const emits = defineEmits(['update: list'])
const add = () =>{
    // props.list.push(newMsg.value)
    const arr = props.list
    arr.push(newMsg.value)
    emits('update:list',arr)
}
</script>

<style lang="css" scoped>

</style>

子组件通过defineEmits定义了一个名为update:list的事件,用于在子组件内部状态改变时,将更新后的数组值传递回父组件。

defineExpose

父组件(列表接收子组件传递的值):

<template>
    <Child ref='childRef' />
    <div class="body">
      <ul>
        <li v-for="item in childRef?.list">{{item}}</li>
      </ul>
    </div>
  </template>
  
  <script setup>
  import Child from './child.vue'
  import {ref} from 'vue'

  const childRef = ref(null)
  </script>
  
  <style lang="css" scoped>
  
  </style>

父组件使用ref创建了一个childRef响应式引用,初始值为null。这个引用将在子组件挂载后指向子组件的实例。

子组件(输入框 传递数据给父组件):

<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)
  }

  defineExpose({list}) // 子组件心甘情愿暴露出来list
  </script>
  
  <style lang="css" scoped>
  
  </style>

子组件通过defineExpose方法暴露了list。父组件可以通过ref引用访问子组件实例,从而访问到list属性。

inject,provide

父组件(列表接收子组件传递的值):

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

   <Child />
</template>
  <script setup>
  import Child from './child.vue'
  import {ref, provide} from 'vue'
  
  const newMsg = ref('')
  const list = ref([])
  
  const add = () => {
    list.value.push(newMsg.value)
  }
  const childRef = ref(null)
  provide('list',list.value)
  </script>
  
  <style lang="css" scoped>
  
  </style>

父组件通过provide函数将list状态暴露出去。provide接受一个对象,对象中的键值对分别是标识符和要提供的数据。

子组件(输入框 传递数据给父组件):

<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= "css" scoped>
  
  </style>

子组件通过inject('list')获取父组件提供的list数据。

总结

Vue.js 的组件通信机制使开发者能够在不同组件之间高效地共享数据和状态。无论是在简单的父子组件间通信还是更复杂的场景下,Vue 提供了灵活且强大的工具来满足各种需求。理解和熟练掌握这些通信模式对于构建可维护和高性能的 Vue 应用程序至关重要。