Vue3组件通信

139 阅读6分钟

props defineProps

  1. 父组件使用v-bind或缩写动态绑定的props,子组件通过props接收父组件传递的数据

  2. Props是一种特别的attributes(属性),你可以在组件上声明注册,这里要用到**defineProps**

  3. defineProps是一个仅<script setup>可用的编译 宏命令并不需要显示地导入。声明的props会自动暴露给模板。defineProps会返回一个对象,其中包含了可以传递给组件的所有props:

        <script setup>
        // defineProps(['title'])
        const props = defineProps(['title'])
        console.log(props.title)
        </script>
        <template>
          <h4>{{ title }}</h4>
        </template>
    
  4. 如果没有使用<script setup>props必须以props选项的方式声明,props对象会作为setup()函数的第一个参数被传入:

        export default {
          props: ['title'],
          setup(props) {
            console.log(props.title)
          }
        }
    
  5. 父子通信例子:

父组件Parent.vue

<script setup>
  // 引入 ref 函数,用于定义响应式数据
  import { ref } from 'vue';
  // 引入子组件 Child.vue
  import Child from './Child.vue';

  // 使用 ref 函数创建了一个响应式的变量 count,初始值为 0,该变量将用于传递给子组件
  let count = ref(0)
</script>

<template>
  <div id="parent">
    <!-- 将 count 变量传递给子组件 Child -->
    <Child :count="count"/>
  </div>
</template>

子组件Child.vue

<script setup>
    // 使用 defineProps 函数定义Props的类型和默认值  不需要引入 直接使用即可
    const props = defineProps({
        // 变量 count 是通过父组件传递过来的
        count: {
            type: Number,
            default: 0
        }
    })
</script>
<template>
  <div id="child">
    <!-- 在模板中可以直接使用该值 -->
    <h1>count: {{ count }}</h1>   
  </div>
</template>
  • defineProps函数的参数中,我们可以传入一个对象,其每一个属性代表一个props
  • 在以上代码中,定义了一个名为count的props,类型为Number,默认值为0
  • 在template中,可以直接使用count变量,它是由父组件传递过来的

$emit defineEmits

  1. 子组件的模板表达式中,可以直接使用$emit方法触发自定义事件(例如:在v-on(简写为@)的处理函数中),还可以提供额外的参数

    父组件可以通过v-on(简写为@)来监听事件,可以简单写一个内联的箭头函数作为监听器;也可以用一个组件方法来作为事件处理函数:

        // 子组件 MyComponent 中
        <button @click="$emit('increaseBy', 1)">click me</button>
    
        // 父组件使用
        <MyButton @increase-by="(n) => count += n" />
    
        <MyButton @increase-by="increaseCount" />
        function increaseCount(n) {
          count.value += n
        }
    

TIP:和原生DOM事件不一样,组件触发的事件没有冒泡机制,你只能监听直接子组件触发的事件。平级组件或是跨越多层嵌套的组件间通信,应使用一个外部的事件总线,或是使用一个全局状态管理方案

  1. 组件可以显示地通过defineEmits()来声明它要触发的事件,$emit方法不能在<script setup>中使用,但defineEmits()会返回一个相同作用的函数供我们使用:

        <script setup>
        const emit = defineEmits(['inFocus', 'submit'])
        function buttonClick() {
          emit('submit')
        }
        </script>
    

    defineEmits()宏不能在子函数中使用。如上所示,它必须直接放置在<script setup>的顶级作用域下

  2. 如果显示地使用了setup函数而不是<script setup>,则事件需要通过emits选项来定义,emit函数也被暴露在setup()的上下文对象上:

        export default {
          emits: ['inFocus', 'submit'],
          setup(props, ctx) {
            ctx.emit('submit')
          }
        }
        // 与setup()上下文对象中的其他属性一样,emit可以安全地被解构
        export default {
          emits: ['inFocus', 'submit'],
          setup(props, { emit }) {
            emit('submit')
          }
        }
    
  3. 父子通信例子:

子组件Child.vue

<script setup>
    const props = defineProps({
        count: {
            type: Number,
            default: 0
        }
    })
    
    // 使用 defineEmit函数定义了一个名
    const emit = defineEmit(['onChildChange'])
    const changeParentCount= () => {
        emit('onChildChange', 5)
    }
</script>
<template>
  <div id="child">
    <h1>count: {{ count }}</h1>   
    <button @click="changeParentCount">更新父组件的count</button>
  </div>
</template>
  • 在 template 的 button 中使用 @click="changeParentCount" 添加点击事件监听器,当按钮被点击时,将调用 changeParentCount 方法,触发父组件中的自定义事件
  • 然后使用 defineEmits 函数定义了一个名为 changeParentCount 的自定义事件。然后通过 emit 方法触发名为 changeParentCount 的自定义事件,并将参数 5 传递给父组件

父组件Parent.vue

<script setup>
  import { ref } from 'vue';
  import Child from './Child.vue';
  let count = ref(0)
  
  // 这个方法用于处理子组件中触发的自定义事件 changeParentCount,并更新父组件中的 count 变量的值。
  const parentChanged= (params) => {
    count.value += params
  }
</script>

<template>
  <div id="parent">
    <!-- 监听子组件自定义事件 changeParentCount -->
    <Child :count="count" @onChildChange="parentChanged"/>
  </div>
</template>
  • 在template中,通过v-on(@)监听子组件自定义事件changeParentCount,并在父组件中执行名为parentChanged的方法,它接收一个名为params的参数,然后更新父组件中的count变量的值

ref和defineExpose

  1. 在Vue3中,ref函数除了可以用于定义一个响应式的变量或引用之外,还可以获取DOM组件实例
  2. defineExpose是用于将组件内部的属性和方法暴露给父组件或其他组件使用。通过这种方法,可以定义哪些部分可以从组件的外部访问和调用
  3. 例子如下:

子组件Child.vue

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

  const msg = ref('我是子组件中的数据');
  const childMethod = () => {
    console.log('我是子组件中的方法');
  }

  // defineExpose 对外暴露组件内部的属性和方法,不需要引入,直接使用
  // 将属性 msg 和方法 childMethod 暴露给父组件
  defineExpose({
    msg,
    childMethod
  })
</script>

父组件Parent.vue

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

  // 获取子组件DOM实例
  const childRef = ref();

  // 该方法用于获取子组件对外暴露的属性和方法
  const getChildPropertyAndMethod = () => {
    // 获取子组件对外暴露的属性
    console.log(childRef.value.msg);
    // 调用子组件对外暴露的方法
    childRef.value.childMethod();
  }
</script>

<template>
  <div id="parent">
    <Child ref="childRef"/>
    <button @click="getChildPropertyAndMethod">获取子组件对外暴露的属性和方法</button>
  </div>
</template>

provide和inject

  • ’prop逐级透传‘:组件不关心这些props,但是为了使能访问到它们,仍然需要定义并向下传递,如果组件链路非常长,可能会影响到更多这条路上的组件

  • provideinject可以帮助我们解决这一问题,一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级多深,都可以注入由父组件提供给整条链路的依赖

Provide(提供)

provied()函数接收两个参数:

  • 注入名(可以是一个字符串或是一个Symbol),后代组件会用注入名来查找期望注入的值,一个组件可以多次调用该函数,使用不同的注入名,注入不同的依赖值
  • (可以是响应式对象、响应式的ref、reactive对象、函数等)
<script setup>
import { provide } from 'vue'

provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
</script>

Inject(注入)

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

const message = inject('message')
</script>

// 如果注入名没有任何组件提供,应该声明一个默认值 
// `value` 会是 "这是默认值"
const value = inject('message', '这是默认值')
  • 父子通信例子:

父组件Parent.vue

<script setup>
  // 引入 provide,用于提供数据给所有子组件
  import { ref, provide } from 'vue';
  // 引入子组件1和子组件2
  import Child1 from './Child1.vue';
  import Child2 from './Child2.vue';

  const message = ref('我是父组件的数据')

  // 使用 provide 将数据 message 提供给所有子组件
  provide('message', message)
</script>

<template>
  <div id="parent">
    <Child1 />
    <Child2 />
  </div>
</template>

组件Child1.vue

<script setup>
  import { inject } from 'vue';
  // 使用 inject 获取来自父组件的数据 message
  const parentMessage = inject('message');
</script>

<template>
  <div id="child">
    <p>子组件1: {{ parentMessage }}</p>
  </div>
</template>

组件Child2.vue

<script setup>
  import { inject } from 'vue';
  // 使用 inject 获取来自父组件的数据 message
  const parentMessage = inject('message');
</script>

<template>
  <div id="child">
    <p>子组件2: {{ parentMessage }}</p>
  </div>
</template>

vueX

pinia

mitt 全局bus总线

在Vue3中,可以使用第三方库mitt实现组件之间的通信

mitt是一个简单且强大的事件总线库(类似Vue2中的EventBus),提供了一种方便的方式来在不同组件之间传递事件和数据

如何实现通信

  1. 安装mitt.js
yarn add mitt
  1. 创建一个 event bus
// mitt/index.js
import mitt from 'mitt';
const bus = mitt();
export default bus;
  1. 在需要通信的组件中,导入event bus对象并进行事件的监听触发
// 组件 First.vue
<script setup>
  import mitt from '../mitt';

  const emitEvent = () => {
    mitt.emit('updateName', 36);
  }
</script>

<template>
  <div id="first">
    <button @click="emitEvent">更新name和age</button>
  </div>
</template>

// 组件 Second.vue
<script setup>
  import mitt from '../mitt';
  import { ref } from 'vue';

  let name = ref('Echo');
  let age = ref(26);

  mitt.on('updateName', (data) => {
    name.value = 'Steven';
    age.value = data;
  });
</script>

<template>
  <div id="second">
    <p>name: {{ name }}</p>
    <p>age: {{ age }}</p>
  </div>
</template>

参考文章:

vue官方文档: 组件基础 | Vue.js

Vue3组件通信的7种方法,值得收藏! - 掘金