项目开发中经常遇到组件通信的场景,那么有哪些方法能实现呢?
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。
意思是在子组件中,没使用 prop
或 emits
定义的 attribute
,可以通过 $attrs
来访问。
常见的有 class
、style
和 id
。
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
也是用来处理 跨组件通信
Pinia
跟 Vuex
相比有以下优点:
- 调用时代码更简洁了
- 对
TS
更友好 - 合并了
Vuex
的Mutation
和Action
。天然支持异步 - 天然分包
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
遇到多层传值时,使用 props
和 emit
显得比较笨拙。这时就可以用 provide
和 inject
了
- provide 是在父组件中定义的方法,用于提供数据给所有子组件。 接收两个参数,第一个参数是字符串或Symbol 类型的键,用于识别提供的数据。第二个参数是要提供的数据本身。这个数据可以是响应式的对象、响应式的 ref、reactive 对象、函数等。父组件中使用 provide 提供数据后,所有的子组件都可以通过 inject 来访问这些数据
- inject 是在子组件中使用的方法,用于接收父组件提供的数据。 它接收一个参数,即要注入的数据的键。在子组件中使用 inject 时,可以直接使用接收到的数据,而不需要在组件的配置选项中声明这些数据
无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者·
父组件示例如下:
<!-- 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
getcurrentinstance
是 vue
提供的一个方法,支持访问内部组件实例。
getCurrentInstance
只暴露给高阶使用场景,典型的比如在库中。强烈反对在应用的代码中使用getCurrentInstance
。请不要把它当作在组合式 API 中获取this
的替代方案来使用。
getCurrentInstance 只能在 setup 或生命周期钩子中调用
<script setup>
import { getCurrentInstance } from 'vue';
const instance = getCurrentInstance();
console.log(instance); // 输出当前组件实例
</script>