likes
comments
collection
share

vue3组件通信方式

作者站长头像
站长
· 阅读数 23
    vue3学了一直没用上,今天来复习一下vue3的组件通信方式有哪些,不喜勿喷

1.props

props 是 Vue 中最常见的父子通信方式,使用起来也比较简单。根据上面的demo,我们在父组件中定义了数据和对数据的操作,子组件只渲染一个列表。

父组件代码如下:

<template>  
    <!-- child component -->  <child-components :list="list"></child-components>  <!-- parent component -->  
    <div class="child-wrap input-group">
            <input v-model="value"  type="text" class="form-control" placeholder="Pleasenter"/>       <div class="input-group-append">
        <button @click="handleAdd" class="btn btn-primary" type="button">add</button>
     </div>  
    </div>
</template>
<script setup>
    import { ref } from 'vue'import ChildComponents from './child.vue'
    const list = ref(['JavaScript', 'HTML', 'CSS'])
    const value = ref('')// event handling function triggered by add
    const handleAdd = () => {
        list.value.push(value.value)
        value.value = ''
    }
</script>

子组件只需要渲染父组件传递的值。

代码如下:

<template>
    <ul class="parent list-group">
        <li class="list-group-item" v-for="i in props.list" :key="i">{{ i }}</li>
    </ul>
</template>
<script setup>
    import { defineProps } from 'vue'
    const props = defineProps({
        list: {
            type: Array,
            default: () => [],
        },
    })
</script>

2.Emit

Emit也是Vue中最常见的组件通信方式,用于子组件向父组件传递消息。

我们在父组件中定义列表,子组件只需要传递添加的值。

子组件代码如下:

<template>
    <div class="child-wrap input-group">
        <input v-model="value" type="text" class="form-control" placeholder="Pleaseenter"/>            <div class="input-group-append">
        <button @click="handleSubmit" class="btn btn-primary" type="button">add</button>              </div>
    </div>
</template>
<script setup>
    import { ref, defineEmits } from 'vue'
    const value = ref('')
    const emits = defineEmits(['add'])
    const handleSubmit = () => {
        emits('add', value.value)
        value.value = ''
    }
</script>

点击子组件中的【添加】按钮后,我们会发出一个自定义事件,并将添加的值作为参数传递给父组件。

父组件代码如下:

<template><!-- parent component -->
    <ul class="parent list-group">
        <li class="list-group-item" v-for="i in list" :key="i">{{ i }}</li>
    </ul> 
    <!-- child component --> 
    <child-components @add="handleAdd"></child-components>
</template>
<script setup>
 import { ref } from 'vue'
 import ChildComponents from './child.vue'
 const list = ref(['JavaScript', 'HTML', 'CSS'])// event handling function triggered by add
 const handleAdd = value => {
     list.value.push(value)
 }
</script>

在父组件中,只需要监听子组件的自定义事件,然后执行相应的添加逻辑即可。

3、v-model

v-model 是 Vue 中一个优秀的语法糖,比如下面的代码。

<ChildComponent v-model:title="pageTitle" />

这是以下代码的简写形式

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

这确实容易了很多。现在我们将使用 v-model 来实现上面的示例。

子组件

<template>
    <div class="child-wrap input-group">
        <input v-model="value" type="text" class="form-control" placeholder="Please enter"/>          <div class="input-group-append">
            <button @click="handleAdd" class="btn btn-primary" type="button">add</button>
        </div>
    </div>
</template>
<script setup>
    import { ref, defineEmits, defineProps } from 'vue'
    const value = ref('')
    const props = defineProps({
    list: {
        type: Array,
        default: () => [],
        },
    })
    const emits = defineEmits(['update:list'])// Add action
    const handleAdd = () => {
        const arr = props.list
        arr.push(value.value)
        emits('update:list', arr)  
        value.value = ''
    }
</script>

在子组件中,我们先定义props和emits,添加完成后再发出指定的事件。

注意:update:*是Vue中固定的写法,*代表props中的一个属性名。

在父组件中使用比较简单,代码如下:

<template>
    <!-- parent component -->
    <ul class="parent list-group">
    <li class="list-group-item" v-for="i in list" :key="i">{{ i }}</li>
    </ul>
    <!-- child component -->
    <child-components v-model:list="list"></child-components>
</template>
<script setup>
    import { ref } from 'vue'
    import ChildComponents from './child.vue'
    const list = ref(['JavaScript', 'HTML', 'CSS'])
</script>

4、Refs

使用API选项时,我们可以通过this.$refs.name获取指定的元素或组件,但在组合API中不行。如果我们想通过ref获取,需要定义一个同名的Ref对象,在组件挂载后可以访问。示例代码如下:

<template>
    <ul class="parent list-group">
        <li class="list-group-item" v-for="i in childRefs?.list" :key="i">{{ i }}</li>
    </ul>
    <!-- The value of the child component ref is the same as that in the <script> --> 
    <child-components ref="childRefs"></child-components> 
    <!-- parent component -->
</template>
<script setup>
    import { ref } from 'vue'
    import ChildComponents from './child.vue'
    const childRefs = ref(null)
</script>

子组件代码如下:

<template>
    <div class="child-wrap input-group">
       <input v-model="value" type="text" class="form-control" placeholder="Please enter"/>            <div class="input-group-append">
          <button @click="handleAdd" class="btn btn-primary" type="button">add</button>               </div>
   </div>
</template>
<script setup>
    import { ref, defineExpose } from 'vue'
    const list = ref(['JavaScript', 'HTML', 'CSS'])
    const value = ref('')// event handling function triggered by add
    const handleAdd = () => {
        list.value.push(value.value)
        value.value = ''
    }
    defineExpose({ list })
</script>

注意:默认情况下,setup 组件是关闭的,通过模板 ref 获取组件的公共实例。如果需要公开,需要通过defineExpose API 公开。

5、provide/inject

provide/inject是 Vue 中提供的一对 API。无论层级多深,API 都可以实现父组件到子组件的数据传递。示例代码如下所示:

父组件

<template>
    <!-- child component -->
    <child-components></child-components>
    <!-- parent component -->
    <div class="child-wrap input-group">
         <input v-model="value" type="text" class="form-control" placeholder="Please enter" />          <div class="input-group-append">
             <button @click="handleAdd" class="btn btn-primary" type="button"> add</button>              </div>
    </div>
</template>
<script setup>
    import { ref, provide } from 'vue'
    import ChildComponents from './child.vue'
    const list = ref(['JavaScript', 'HTML', 'CSS'])
    const value = ref('')// Provide data to child components.
    provide('list', list.value)// event handling function triggered by add
    const handleAdd = () => {
        list.value.push(value.value)
        value.value = ''
    }
</script>

子组件

<template>
    <ul class="parent list-group">
        <li class="list-group-item" v-for="i in list" :key="i">{{ i }}</li>
    </ul>
</template>
<script setup>
import { inject } from 'vue'// Accept data provided by parent component
const list = inject('list')
</script>

注意:使用 provide 进行数据传输时,尽量使用 readonly 封装数据,避免子组件修改父组件传递的数据。

6、eventBus Vue 3 中移除了 eventBus,但可以借助第三方工具来完成。Vue 官方推荐使用 mitt 或 tiny-emitter。在大多数情况下,不建议使用全局事件总线来实现组件通信。虽然比较简单粗暴,但是维护事件总线从长远来看是个大问题。

bus.js

//引入mitt插件:mitt一个方法,方法执行会返回bus对象
import mitt from 'mitt';
const $bus = mitt();
export default $bus;

父组件

<template>
  <div class="box">
    <h1>全局事件总线$bus</h1>
    <hr />
    <div class="container">
      <Child1></Child1>
      <Child2></Child2>
    </div>
  </div>
</template>

<script setup lang="ts">
//引入子组件
import Child1 from "./Child1.vue";
import Child2 from "./Child2.vue";
</script>

子组件

<template>
  <div class="child2">
     <h2>我是子组件2:曹丕</h2>
     <button @click="handler">点击我给兄弟送一台法拉利</button>
  </div>
</template>

<script setup lang="ts">
//引入$bus对象
import $bus from '../../bus';
//点击按钮回调
const handler = ()=>{
  $bus.emit('car',{car:"法拉利"});
}
</script>

子组件

<template>
  <div class="child1">
    <h3>我是子组件1:曹植</h3>
  </div>
</template>

<script setup lang="ts">
import $bus from "../../bus";
//组合式API函数
import { onMounted } from "vue";
//组件挂载完毕的时候,当前组件绑定一个事件,接受将来兄弟组件传递的数据
onMounted(() => {
  //第一个参数:即为事件类型  第二个参数:即为事件回调
  $bus.on("car", (car) => {
    console.log(car);
  });
});
</script>

7、vuex/pinia Vuex 和 Pinia 是 Vue 3 中的状态管理工具,使用这两个工具可以轻松实现组件通信。由于这两个工具都比较强大,这里就不一一展示了。有关详细信息,请参阅文档。

讨论:

  1. 什么时候用evnetbus?
  2. 已经有了vuex,为什么还要用pinia?