Vue3组件通信方式有哪些?

99 阅读5分钟

项目开发中经常遇到组件通信的场景,那么有哪些方法能实现呢?

Vue3组件通信

props

props是最常见的父子组件通信方式,父组件将参数通过props传递给子组件,子组件只需要渲染父组件传递的值即可

我们在父组件中将message变量传给子组件:

<template>
  <div>
    <ChildComponent :message="parentMessage" />
  </div>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
const parentMessage = 'Hello from parent';
</script>

然后在子组件中接收父组件的传值message并将其渲染到页面:

<template>
  <div>
    {{ message }}
  </div>
</template>
<script setup>
const props = defineProps({
  message: String
});
</script>

slot

插槽可以理解为传一段 HTML 片段给子组件。子组件将 <slot> 元素作为承载分发内容的出口。

父组件如下所示,通过slot将内容

Hello from parent

传递给子组件

<template>
  <div>
    <ChildComponent>
        <h1>Hello from parent</h1>
    </ChildComponent>
  </div>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
</script>

子组件如下所示,子组件使用 表示插槽,父组件的内容会被放在这个位置

<template>
  <div>
    <slot></slot>
  </div>
</template>
<script setup>
import { defineSlots } from 'vue'
defineSlots(['default'])
</script>

以上是插槽的基本用法,还有更多的用法可参考官方文档

$emit

子组件通知父组件触发一个事件,并且可以传值给父组件

props 一样,在 <script setup> 中必须使用 defineEmits API 来声明 emits,它具备完整的推断并且在 <script setup> 中是直接可用的 ,在 <script setup> 中,defineEmits 不需要另外引入。

父组件:

<template>
  <div>
    <ChildComponent @childClicked="handleChildClick" />
  </div>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
const handleChildClick = (payload) => {
  console.log(payload); // 'Hello from child'
};
</script>

子组件:

<template>
  <button @click="notifyParent">Click me</button>
</template>
<script setup>
const emit = defineEmits(['childClicked']);
const notifyParent = () => {
  emit('childClicked', 'Hello from child');
};
</script>

$attrs

所谓的 Non-Props 就是 非 Prop 的 Attribute。

意思是在子组件中,没使用 propemits 定义的 attribute,可以通过 $attrs 来访问。

常见的有 classstyleid

withDefaults 用于为 props 设置默认值。useContext 是一个可以访问当前组件 context 的函数,其中包含 attrs 属性用于获取未明确声明为 props 的 attribute 绑定。

<template>
  <div v-bind="attrs">
    {{ message }}
  </div>
</template>
<script setup>
import { withDefaults } from 'vue'
const props = withDefaults(defineProps({
  message: String
}), {
  message: 'Hello World'
});
const attrs = useContext().attrs;
</script>

v-model

是vue中一个优秀的语法糖,v-model 可以同时支持多个数据双向绑定。 借助props和emit实现双向数据绑定

父组件:

<template>
  <ChildComponent v-model="parentValue" />
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
let parentValue = 'Hello World';
</script>

子组件:

<template>
  <input :value="modelValue" @input="updateValue" />
</template>
<script setup>
const props = defineProps({
  'modelValue': {
    type: String,
    default: ''
  }
});
const emit = defineEmits(['update:modelValue']);
const updateValue = (event) => {
  emit('update:modelValue', event.target.value);
};
</script>

vuex

解决 跨组件通信 的问题

我们使用useStore获取到vuex store的示例,然后使用computed方法创建计算属性count,它会自动根据store.state.count的变化而更新。最后定义increment方法

<template>
  <div>
    {{ count }}
    <button @click="increment">Increment</button>
  </div>
</template>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const count = computed(() => store.state.count);
const increment = () => {
  store.commit('increment');
};
</script>

vuex更多用法可参考官网

pinia

Pinia 也是用来处理 跨组件通信

PiniaVuex 相比有以下优点:

  • 调用时代码更简洁了
  • TS 更友好
  • 合并了 VuexMutationAction 。天然支持异步
  • 天然分包

Pinia 简化了状态管理模块,只用这3个东西就能应对日常大多任务

  • state:存储数据的仓库
  • getters:获取和过滤数据(跟 computed 有点像)
  • actions:存放 “修改 state ”的方法

我们首先创建一个名为counter的pinia store:

import { defineStore } from 'pinia'
export const useCounterStore = defineStore({
  id: 'counter',
  state: () => ({
    count: 0
  }),
  actions: {
    increment() {
      this.count++;
    }
  }
});

在组件中,我们可以这样使用:

<template>
  <div>
    {{ count }}
    <button @click="increment">Increment</button>
  </div>
</template>
<script setup>
import { useCounterStore } from '@/store/counter'
const store = useCounterStore();
const count = store.count;
const increment = store.increment;
</script>

pina更多用法参考官网

mitt

Vue2.x 使用 EventBus 进行组件通信,而 Vue3.x 推荐使用 mitt.js。

比起 Vue 实例上的 EventBus,mitt.js足够小,仅有200bytes;其次mitt支持全部事件的监听和批量移除,不依赖 Vue 实例,所以可以跨框架使用

mitt.js 提供了很多的方法:

  • on:添加事件
  • emit:执行事件
  • off:移除事件
  • clear:清除所有事件

mitt.js 不是专门给 Vue 服务的,但 Vue 可以利用 mitt.js 做跨组件通信。

可以直接导入mitt,创建一个事件发射器实例:

<script setup>
import mitt from 'mitt';
const emitter = mitt();
// 监听事件
emitter.on('foo', e => console.log('foo', e));
// 触发事件
emitter.emit('foo', { a: 'b' });
// 删除事件
emitter.off('foo', e => console.log('foo', e));
</script>
  • emitter.on 监听事件
  • emitter.emit 触发事件
  • emitter.off 删除事件

PS:Mitt 实例不受 Vue 的响应系统管理,因此它们不会自动响应数据的改变。需要手动调用 emitter.emit 触发

更多用法可参考npm官网

provide&inject

遇到多层传值时,使用 propsemit 显得比较笨拙。这时就可以用 provideinject

  • provide 是在父组件中定义的方法,用于提供数据给所有子组件。 接收两个参数,第一个参数是字符串或Symbol 类型的键,用于识别提供的数据。第二个参数是要提供的数据本身。这个数据可以是响应式的对象、响应式的 ref、reactive 对象、函数等。父组件中使用 provide 提供数据后,所有的子组件都可以通过 inject 来访问这些数据
  • inject 是在子组件中使用的方法,用于接收父组件提供的数据。 它接收一个参数,即要注入的数据的键。在子组件中使用 inject 时,可以直接使用接收到的数据,而不需要在组件的配置选项中声明这些数据

无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者·

image.png

父组件示例如下:

<!-- Parent.vue -->
<template>
  <Child />
</template>
<script setup>
import { provide } from 'vue';
import Child from './Child.vue';
const value = 'Hello from parent';
provide('key', value); // key是这个值的标志符
</script>

子组件中,使用inject注入这个值

<!-- Child.vue -->
<template>
  <div>{{ value }}</div>
</template>
<script setup>
import { inject } from 'vue';
const value = inject('key');
</script>

ref&defineExpose

子组件可以通过 expose 暴露自身的方法和数据。

父组件通过 ref 获取到子组件并调用其方法或访问数据。

<script setup> 中,defineExpose 不需要另外引入。使用defineExpose暴露count和increment到组件实例上,这样可以在组件外部通过组件实例访问到这些属性和方法

<template>
  <div>{{ count }}</div>
  <button @click="increment">Increment</button>
</template>
<script setup>
import { ref } from 'vue';
let count = ref(0);
let increment = () => {
  count.value++;
};
defineExpose({ count, increment });
</script>

getCurrentInstance

getcurrentinstancevue 提供的一个方法,支持访问内部组件实例。

getCurrentInstance 只暴露给高阶使用场景,典型的比如在库中。强烈反对在应用的代码中使用 getCurrentInstance。请不要把它当作在组合式 API 中获取 this 的替代方案来使用。

getCurrentInstance 只能在 setup 或生命周期钩子中调用

<script setup>
import { getCurrentInstance } from 'vue';
const instance = getCurrentInstance();
console.log(instance); // 输出当前组件实例
</script>