likes
comments
collection
share

“Vue.js组件对话录:当奶爸组件遇上熊孩子组件,五花八门的沟通艺术”

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

在Vue.js中,组件间的通信是构建复杂应用的关键。无论是父子组件之间的信息传递,还是子组件向父组件的反馈,掌握有效的通信策略至关重要。本文将深入探讨五种常用的组件通信模式,包括代码示例和解析,以帮助开发者更好地理解和应用这些技术。

 “Vue.js组件对话录:当奶爸组件遇上熊孩子组件,五花八门的沟通艺术”

一、父子通信:Props

在Vue.js中,父组件向子组件传递数据最常用的方法是通过props。父组件将数据作为属性传递给子组件,子组件通过defineProps来声明接受哪些props。

代码示例:

// 父组件
<template>
    <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>
// 子组件
<script setup>
const props = defineProps({
    list:{
    type:Array,
    default: () => []
    }
})
</script>

解析: 当父组件渲染时,它会将list的值传递给Child组件作为list属性。子组件可以通过props.list访问这个列表。如果父组件中的list发生变化(例如,通过add方法添加新元素),子组件中的list属性也会相应地更新,因为list是一个响应式的引用。

二、父子通信:Provide/Inject

另一种父子通信方式是使用provideinject。这种方法允许组件树中的祖先组件向下提供数据,而不管层级有多深,后代组件都可以注入这些数据。

代码示例:

// 祖先组件
<script setup>
import { provide, ref } 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>
// 后代组件
<script setup>
import { inject } from 'vue';

const list = inject('list');
</script>

解析:

  • 提供数据:祖先组件使用provide函数来“发布”或“暴露”数据,这样就可以在整个组件树中被后代组件访问。
  • 注入数据:后代组件使用inject函数来“订阅”或“接收”祖先组件提供的数据,而无需直接与祖先组件交互或通过中间组件传递props。

这种方法简化了多层嵌套组件间的通信,尤其是在大型应用中,避免了因过多的props传递导致的组件间耦合度增加。然而,过度使用provideinject可能导致组件之间的关系变得模糊不清,因此,在使用时应确保其必要性,并且保持良好的代码组织和注释。

三、子父通信:事件监听

子组件可以通过触发事件来向父组件发送信息,父组件则通过监听这些事件来接收信息。这是子组件向父组件传递数据的常见方式。

代码示例:

// 父组件
<template>
    <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>
//子组件
 <script setup>
 import { ref,computed,watch, onBeforeUpdate, onUpdated }  from 'vue'

 const list = ref(['html', 'css']) //这个list要变成_list,若使用下面的第一钟方法
 const props = defineProps({
    msg:''
 })

const list = computed(() => {
    _list.value.push(props.msg)
    return [..._list.value]
})

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

onBeforeUpdate (() => {
    list.value.push(props.msg)
 
})
看看看看看看
onUpdated(() => {
    list.value.push(props.msg) //不太可取,会导致无限循环
})
 </script>

1.使用computed更新列表

  • 优点: 1. computed属性是响应式的,它会根据其依赖项(这里是props.msg_list)的变化自动重新计算。 2. 通过返回一个新的数组副本,可以避免直接修改原始数组带来的副作用。
  • 缺点: 1.每当props.msg变化时,computed属性会重新计算,这可能触发组件的多次重新渲染,尤其是如果list在模板中被频繁使用的话。 2._list.value.push(props.msg)直接修改了_list的值,这在computed中并不是最佳实践,因为它会导致不必要的计算。

2.使用watch更新列表

  • 优点: 1. watch可以监听特定的响应式引用,并在它们变化时执行回调。 2. 直接在watch的回调中更新list,可以更精确地控制何时以及如何更新。
  • 缺点: 1. 如果list本身也是一个响应式引用,直接在watch中修改它可能引发不必要的重新渲染。 2. watch的回调函数中修改状态可能会导致数据状态和UI状态之间的不同步,特别是在复杂的状态管理中。

3.使用onBeforeUpdate更新列表

  • 优点: 1. onBeforeUpdate在组件更新之前执行,可以用来做一些准备工作,如更新状态。 2. 它确保了在组件重新渲染前状态已经更新,理论上可以避免无限循环的问题。
  • 缺点: 1. 直接在onBeforeUpdate中修改状态可能导致组件状态和UI状态之间的不同步,尤其是在组件嵌套或状态依赖复杂的情况下。 2. 如果list的更新触发了额外的重新渲染,onBeforeUpdate将再次被调用,这可能导致无限循环,正如你提到的。

以上方法讲完后,让我们来看看另一种好用的方法:

// 父组件
<template>
    <Child @addMsg="handle"/>
</template>

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

const list = ref(['html', 'css'])
const handle = (e) => {
    // console.log(e);
    list.value.push(e);
}
</script>
// 子组���
<script setup>
import { ref } from 'vue';

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

使用自定义事件(Event Emitter)

  • 优点1. 动态更新:子组件可以实时地通知父组件数据的变化,父组件可以根据这些变化做出响应。 2. 解耦:子组件不知道也不需要知道父组件的内部状态,仅通过事件传递数据,这有助于保持组件的独立性。
  • 缺点1. 性能考虑:频繁的事件触发可能会导致父组件不必要的渲染。 2. 调试难度:事件的传递路径不总是直观的,可能增加调试难度。

四、子父通信:V-Model

v-model是一种特殊的语法糖,用于双向数据绑定。当用于子组件时,它允许子组件更新父组件的数据,同时父组件可以监听这些变化。

代码示例:

// 父组件
<template>
    <Child v-model:list="list"/> //体现之处
</template>

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

const list = ref(['html', 'css'])
</script>
// 子组件
<script setup>
import { ref } from 'vue'

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

const emits = defineEmits(['update:list'])
const newMsg = ref('')
const add = () => {   
    const arr = props.list
    arr.push(newMsg.value)
    emits('update:list', arr)
}
</script>

解析: 在子组件中,当add方法被调用时,它会更新本地的arr数组,并通过emits('update:list', arr)触发一个update:list事件,将更新后的数组发送给父组件。v-model在父组件中监听这个事件,并自动更新其绑定的list变量。 因此,尽管看起来像是直接操作了父组件的list,但实际上子组件通过emits触发事件的方式通知父组件更新数据,从而实现了数据的双向绑定。

五、子父通信:Ref与DefineExpose

父组件可以通过ref获取子组件的引用,进而访问子组件中通过defineExpose暴露出来的数据或方法。

代码示例:

// 父组件
<template> 
    <Child ref="childRef"/>
</template>

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

const childRef =  ref(null)
</script>
// 子组件
<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>

解析: 当父组件挂载完成后,onMounted钩子会被调用。此时,childRef已经指向了子组件的实例。由于子组件使用defineExpose暴露了list,父组件现在能够通过childRef.value.list访问子组件的list数据。 这意味着父组件可以在任何时候读取或修改子组件的list属性,只要它是通过defineExpose明确暴露出来的。这是一种强大的机制,允许在组件之间进行更深层次的交互,但应当谨慎使用,以避免破坏组件之间的封装性可维护性

六、结论

组件间的通信是Vue.js应用中不可或缺的一部分,选择合适的通信方式取决于具体的应用场景和组件结构。通过上述五种通信模式的介绍和示例,开发者可以更灵活地构建和维护Vue.js应用,确保数据流的畅通无阻,提升应用的性能和用户体验。随着项目复杂度的增加,理解并熟练运用这些通信策略,将帮助开发者更加高效地解决问题,构建出更加健壮和可维护的Vue.js应用。

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