Vue3 组件通信的8种方式
1. props
父传子,直接传值 ,用v-bind绑定值传递给孩子, 子传父,父亲写方法传递给孩子,孩子接收方法然后调用并把值传递给父
2. 自定义事件
3. mitt(不常用,知道就行)
3.1 安装mitt
npm i mitt
3.2 使用mitt
import mitt from 'mitt'
const emitter = mitt() //emitter 能绑定事件,解绑事件,触发事件
export default emitter
// 导出emitter后,在mian.ts中引入供后续使用
//emitter 共有四个方法
emitter.all() //拿到所有绑定的事件
emitter.on('事件名', callback) //绑定事件
emitter.emit('事件名', callback) //触发事件
emitter.off('事件名', callback) //解绑事件
4. v-model(重要)
当我们在开发的时候,我们在DOM标签,自定义组件,UI库组件上使用的都是 v-model="xxxxxx"
,但是对于它为什么能够双向绑定数据,我们还是可以知道一下。
Vue 2的v-model
底层:
<template>
<input v-model="username" />
// Vue2 底层
<input :value="username" @input = "username = $event.target.value" />
</template>
<script>
let username = ref("张三")
</script>
<style></style>
Vue 3的v-model
底层(对于2有些小改动):
<template>
//自定义组件
<MyInput v-model="username" />
//当然也可以修改modelValue的值,当我们这样写了之后,底层的modelValue都被被替换成title,知道就行
<MyInput v-model:title="username" />
// Vue3 底层,
<MyInput :modelValue="username" @update = "username = $event" />
</template>
<script setup>
let username = ref("张三")
</script>
<style></style>
// MyInput 组件,结合props + 自定义事件进行组件间通信,通常来说是UI库设计人员写的底层代码,我们作为开发会用即可,但是能了解它的底层逻辑还是对我们写代码很有帮助的
<template>
<input type="text" :value="modelValue" @input = "emits('update:modelValue', $event.target.value)"
</template>
<script setup>
defineProps(['modelValue'])
const emits = defineEmits(['update:modelValue'])
</script>
<style></style>
最后对于这个点再说说$event
:
- 对于原生事件,
$event
就是事件对象,能$event.target.value
- 对于自定义事件,
$event
就是触发事件时所传递的数据,不能.target
5. $attrs(不推荐使用,知道就行)
通用于祖传孙,通过父组件(中间组件)作为桥梁传递,祖传递的数据,如果不被defineProps
接收,那么都会被$attrs
接收
6. Provide、inject依赖注入(重要,需要掌握)
对于多层级嵌套的组件我们使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦,provide
和 inject
可以帮助我们解决这一问题
Provide (提供):
<script setup>
import { provide, ref } from 'vue'
//1. 注入名可以是一个字符串或者是Symbol(Es6),使用Symbol()函数可以返回一个独一无二的值
//2.一个组件可以多次调用 provide(),使用不同的注入名,注入不同的依赖值
//3.后代组件会用注入名来查找期望注入的值
//4.第二个参数是提供的值,值可以是任意类型,包括响应式的状态,比如一个 ref (important!!!)
const message = ref('hello!')
provide(/* 注入名 */ 'message', /* 值 */ message)
//我们既可以在一个组件中提供依赖,还可以在整个应用层面提供依赖,在应用级别提供的数据在该应用内的所有组件中都可以注入
const app = createApp({})
app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
</script>
Inject (注入):
- 如果注入的值是一个ref,那么注入进来的就是一个ref对象,不会自动解包为其内部的值,能够一直保持响应性链接。
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>
- 如果
inject
传入的注入名没有被祖先链上的组件提供,会出现警告,这种情况的话我们需要声明一个默认值
// 如果没有祖先组件提供 "message"
// `value` 会是 "这是默认值"
const value = inject('message', '这是默认值')
- 如果想要提供的数据不被注入方更改,使用
readonly()
来包装提供的值
<script setup>
import { ref, provide, readonly } from 'vue'
const count = ref(0)
provide('read-only-count', readonly(count))
</script>
- 尽可能将任何对响应式状态的变更都保持在供给方组件中
7. Pinia(重要,需要掌握)
关于Pinia其实也很简单,大致核心就是在Vue3中:
ref
就是state
属性computed()
就是getters
function()
就是actions
- 采用
defineStore()
定义,返回值是UsexxxStore
第一个参数约定俗成为store文件夹中当前文件的名称,当作唯一ID,第二个参数在Vue3中我们传入一个箭头函数(()=> { }) - 我们需要将定义的
ref
,computed()
,function()
, 通过一个对象return暴露出去,供外部组件使用
更多的细节大家可以去官网查看,传送门 Pinia
8. slot-插槽(重要,需要掌握)
-
默认插槽
<FancyButton> Click me! <!-- 插槽内容,可以是任意的模板内容,甚至可以是组件 --> </FancyButton>
<button class="fancy-btn"> <slot></slot> <!-- 插槽出口,相当于插槽内容的占位符 --> </button>
父组件没有提供任何插槽内容时,只需要在slot标签之间加默认值,即
<slot>默认值</slot>
,默认值会被作为默认内容渲染,如果我们提供了插槽内容,那么被显式提供的内容会取代默认内容。默认插槽其实也是一种具名插槽,它的名字是
<slot name="defalult"/>
-
具名插槽
具名插槽就是给每个插槽取个独一无二的名字,以保证在有多个插槽出口的时候,每个出口能渲染不一样的内容,说白了就是一个萝卜一个坑<div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>
要为具名插槽传入内容,我们需要使用一个含
v-slot
指令的<template>
元素,并将目标插槽的名字传给该指令,如下:<BaseLayout> <template v-slot:header> <!-- header 插槽的内容放这里 --> </template> </BaseLayout> //v-slot对应的简写 <BaseLayout> <template #header> <!-- header 插槽的内容放这里 --> </template> </BaseLayout>
当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非
<template>
节点都被隐式地视为默认插槽的内容<BaseLayout> <template #header> <h1>头部</h1> </template> <!-- 隐式的默认插槽 --> <p>任意内容.</p> <p>另外一段.</p> <template #footer> <p>尾部</p> </template> </BaseLayout>
-
作用域插槽
由于插槽的内容无法访问到子组件的数据(数据),而我们想要使用父组件域内和子组件域内的数据,需要给slot传递数据,父组件进行接收
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
// 子组件传递过来的数据都会在slotProps对象中
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
//也可以解构
<MyComponent v-slot="{text, count}">
{{ text }} {{ count }}
</MyComponent>
具名作用域插槽用法:
<div>
<slot name="mesg" :text="greetingMessage" :count="1"></slot>
</div>
<MyComponent v-slot:mesg="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
//有时候你也会看到这种简写形式,是一样的
<MyComponent #mesg="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
默认作用域插槽用法:
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
<MyComponent #default="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
v-slot:xxx
就是选择插槽,v-slot="xxx"
就是子组件传的值,这句话很关键,能理解了说明你也懂插槽了