Vue3组件常用通信方式
1.props
可以实现父子组件,子父组件,兄弟组件通信。
defineProps方法可以接收父组件传递的数据,具体使用如下:
父组件:
<template>
<div>
<Child info = "enen" :money = "money"></Child>
</div>
</template>
<script setup lang = 'ts'>
import Child from './Child.vue';
import { ref } from 'vue';
let money = ref(1000)
</script>
子组件:
<template>
<div>
<h1>我是子组件</h1>
<p>{{ props.info }}</p>
<p>{{ props.money }}</p>
<!-- 可以省略props -->
<p>{{ info }}</p>
<p>{{ money }}</p>
</div>
</template>
<script setup lang = "ts">
// 需要使用到defineProps方法接收父组件传递过来的数据
// defineProps是vue3提供的方法 无需引入
//此处对象|数组写法都可以
let props = defineProps({info:String,money:Number})
// let props = defineProps(['info','money'])
console.log(props)
</script>
2.自定义事件
可以实现子父组件通信。
利用defineEmits方法返回函数触发自定义事件。
父组件:
<template>
<div>
<Event2 @handler2 = "handler2"></Event2>
</div>
</template>
<script setup lang = 'ts'>
import Event2 from './Event2.vue';
const handler2 = (param1,param2)=>{
console.log(param1,param2)
}
</script>
子组件:
<template>
<div>
<button @click="handler">点击按钮触发事件handler</button>
</div>
</template>
<script setup lang = 'ts'>
// 利用defineEmits方法返回函数触发自定义事件 无需引入直接使用
let $emit = defineEmits(['handler2'])
const handler = ()=>{
// 第一个参数为事件类型,剩余参数为给父组件传递的数据
$emit('handler2','传给父组件的参数一','传给父组件的参数二')
}
</script>
3.全局事件总线
mitt方法使用:
(1)安装
(2)引入使用
子组件1:通过emit触发目标组件上的事件,并且传递数据
<template>
<div>
<h1>子组件1</h1>
<button @click="handler">向子组件2传递数据</button>
</div>
</template>
<script setup lang = 'ts'>
import $bus from '../../bus';
const handler = (() => {
$bus.emit('car', '传递给Child2的数据')
})
</script>
子组件2:通过$bus.on方法绑定事件,接收数据
<template>
<div>
<h1>子组件2</h1>
</div>
</template>
<script setup lang = 'ts'>
import { onMounted } from 'vue';
import $bus from '../../bus';
console.log($bus)
onMounted(() => {
// 第一个参数为事件类型,第二个参数为回调函数
$bus.on('car', (car) => {
console.log(car)
})
})
</script>
4.v-model
v-model 在vue3中可以实现父子组件间的数据通信。
父组件:
<template>
<div>
<h1>v-model info:{{ info }} {{ a }} {{ b }}</h1>
<!-- <input type="text" v-model="info"> -->
<!-- <Child :modelValue="info" @update:modelValue="handler"></Child> -->
<!--
v-model的使用:
1.相当于给子组件传递props[modelValue] = 10000
2.相当于给子组件绑定自定义事件update:modelValue
-->
<Child v-model="info"></Child>
<!-- 同一组件上可以绑定多个v-model -->
<Child1 v-model:a="a" v-model:b="b"></Child1>
</div>
</template>
<script setup lang = 'ts'>
// v-model 收集表单数据,实现数据双向绑定
// v-model 也可以实现组件之间的通信,实现父子组件数据同步的业务
// 父组件向子组件传递数据 Props
// 子组件向父组件传递数据 自定义事件
import { ref } from 'vue'
import Child from './Child.vue';
import Child1 from './Child1.vue'
let a = ref(1)
let b = ref(3)
let info = ref(100000)
// 自定义事件接收子组件向父组件传递的数据
const handler = (n) => {
console.log('子组件向父组件传递过来的数据是:', n)
console.log(info, 'info')
info.value = n
}
</script>
子组件:
<template>
<div>
<h1>money{{ modelValue }}</h1>
<button @click="handler">父子组件数据同步</button>
</div>
</template>
<script setup lang = 'ts'>
// 接收父组件数据
let props = defineProps(['modelValue'])
let $emit = defineEmits(['update:modelValue'])
const handler = () => {
$emit('update:modelValue', props.modelValue + 10000)
}
</script>
子组件1:
<template>
<div>
<button @click="handler">a{{ a }}</button>
<button>b{{ b }}</button>
</div>
</template>
<script setup lang = 'ts'>
let props = defineProps(['a', 'b'])
let $emit = defineEmits(['update:a', 'update:b'])
const handler = () => {
$emit('update:a', props.a + 1)
}
</script>
注意:
在使用v-model实现父子组件通信时,子组件默认的prop为 modelValue, 对应的事件为update:modelValue,若想要自行命名,则可参考官方文档,如下图所示。
5.useAttrs
vue3中可以获取组件上的属性和时间的方法,也可用于组件通信。
父组件:
<template>
<div>
<h1>Attrs-listener</h1>
<el-button type="primary" size="small" :icon="Edit"></el-button>
<HintButton type="primary" size="small" :icon="Edit" title="title" @click="handler" @getNum="handler"></HintButton>
</div>
</template>
<script setup lang = 'ts'>
// vue3框架提供的一个useAttrs方法,可以获取组件上的属性和方法
import {
Check,
Delete,
Edit,
Message,
Search,
Star,
} from '@element-plus/icons-vue'
import HintButton from './HintButton.vue';
const handler = () => {
console.log(123345)
}
</script>
子组件:
<template>
<div :title="$attrs.title">
<el-button :="$attrs"></el-button>
</div>
</template>
<script setup lang = 'ts'>
// 引入useAttrs方法 获取组件上面的属性与方法
import { useAttrs } from 'vue';
let $attrs = useAttrs()
console.log($attrs, '组件hintButton1')
// defineProps和useAttrs都可以获取到组件上的属性和事件
//但是用defineProps接收后 useAttrs就获取不到对应的属性或者方法了
// 例如执行 defineProps(['title']) 之后,使用useAttrs无法再获取到title
</script>
6.ref 和 parent
ref可以获取子组件实例。
父组件代码:
<template>
<div>
<h1>父组件:{{ money }}</h1>
<button @click="handler">
跟儿子借十块钱
</button>
<Son ref="son"></Son>
<Dau></Dau>
</div>
</template>
<script setup lang = 'ts'>
import Son from './Son.vue'
import Dau from './Daughter.vue'
import { ref } from 'vue'
let money = ref(100000000)
// ref:可以获取真实的DOM节点,可以获取到子组件实例VC
// $parent:可以在子组件内部获取到父组件的实例
// 获取子组件实例
let son = ref()
const handler = () => {
money.value += 10
son.value.money -= 10
// console.log(son.value);
son.value.fly()
}
// 使用defineExpose方法将money暴露给Dau
defineExpose({
money
})
</script>
子组件Son中需要使用defineExpose方法将数据暴露出去以供父组件获取:
<template>
<div>
儿子组件{{ money }}
</div>
</template>
<script setup lang = 'ts'>
import { ref } from 'vue'
let money = ref(22222222)
const fly = () => {
console.log('fly')
}
defineExpose({
money,
fly
})
</script>
子组件Dau通过parent获取父组件实例:
<template>
<div>
<h2>女儿组件{{ money }}</h2>
<button @click="handler($parent)">跟父组件借10000</button>
</div>
</template>
<script setup lang = 'ts'>
import { ref } from 'vue'
let money = ref(20000000)
const handler = ($parent) => {
money.value += 10000
$parent.money -= 10000
console.log($parent, '父组件')
}
</script>
7.provide 与 inject
provide 与 inject 方法可以实现祖先组件与后代组件之间的数据传递,即在祖先组件中 provide 方法提供数据,后代组件中使用 inject 方法注入数据。
祖先组件代码:
<template>
<div>
<h1>Provide-inject</h1>
<div>给后代组件{{ apple }}</div>
<Child></Child>
</div>
</template>
<script setup lang = 'ts'>
import Child from './Child.vue';
import { ref, provide } from 'vue'
let apple = ref('苹果')
// provide需要上传两个参数
// 第一个参数是提供数据的Key值
// 第二个参数是需要提供的数据
provide('Key', apple)
</script>
后代组件:
<template>
<div>孙子组件接收到了{{ apple }}</div>
<button @click="updateApple">更新数据</button>
</template>
<script setup lang = 'ts'>
import { inject } from 'vue';
// 注入祖先组件提供数据
// 需要传参数:即为祖先提供数据的Key
let apple = inject('Key')
console.log(apple)
const updateApple = () => {
apple.value = '香蕉'
}
</script>
8.pinia
pinia:集中式管理状态容器,可以实现任意组件之间的通信。
pinia需要安装后使用,在项目根目录下执行 npm install pinia。
创建store文件夹 ,创建文件index引入pinia
在main.ts 文件中全局使用
info.ts小仓库代码
// 定义info小仓库
import { defineStore } from 'pinia'
// defineStore接收两个参数
// 第一个:小仓库名称 第二个:小仓库配置对象
// defineStore 方法执行会返回一个函数,函数作用是让组件可以获取到仓库数据
let useInfoStore = defineStore("info", {
state: () => {
return {
count: 10000,
arr: [1, 2, 3, 4, 5, 6, 7]
}
},
actions: {
// 注意:与vuex相比函数没有context上下文对象
// 没有commit,没有mutations修改数据
updateNum(a: number, b: number) {
this.count += b
}
},
getters: {
total() {
let result: any = this.arr.reduce((prev: number, next: number) => {
return prev + next
}, 0)
return result
}
}
})
export default useInfoStore;
在子组件一中使用info 中的数据
<template>
<div>
子组件Child
<h4>{{ infoStore.count }} ---{{ infoStore.total }}</h4>
<button @click="updateInfo">更新数据</button>
</div>
</template>
<script setup lang = 'ts'>
import useInfoStore from '../../store/modules/info';
// 获取小仓库对象
let infoStore = useInfoStore()
const updateInfo = () => {
// infoStore.count++
// infoStore.$patch({
// count: 888888
// })
// 可以调用仓库的方法修改仓库的数据
infoStore.updateNum(66, 88)
console.log(infoStore)
}
</script>
在子组件二中使用info中的数据:
<template>
<div>
子组件Child1:{{ infoStore.count }}--{{ todoStore.arr }} ----{{ todoStore.total }}
</div>
<div @click="updateTodo">{{ todoStore.todos }}</div>
</template>
<script setup lang = 'ts'>
import useInfoStore from '../../store/modules/info';
import useTodoStore from '../../store/modules/todo';
// 获取小仓库对象
let infoStore = useInfoStore()
let todoStore = useTodoStore()
const updateTodo = () => {
todoStore.updateData()
}
</script>
也可以使用组合式API来实现仓库创建。
// 组合式API
import { defineStore } from "pinia";
import { ref, computed } from "vue";
let useTodoStore = defineStore('tode', () => {
let todos = ref([{ id: 1, title: '上班' }, { id: 2, title: '摸鱼' }, { id: 1, title: '打游戏' }])
let arr = ref([1, 2, 3, 4, 5, 6])
const total = computed(() => {
return arr.value.reduce((prev, next) => {
return prev + next
}, 0)
})
return {
todos,
total,
arr,
updateData() {
todos.value.push({ id: 4, title: '写bug' })
}
}
})
export default useTodoStore;
以上及实现组件间的数据通信。
9.slot
默认插槽,具名插槽,作用域插槽。
父组件:
<template>
<h1>Slot</h1>
<!-- 作用域插槽 -->
<Test1 :todos="todos">
<template v-slot="{ $row, $index }">
<h1 :style="{ color: $row.done ? 'red' : 'green' }">{{ $row.title }} --- {{ $index }}</h1>
</template>
</Test1>
<!-- 默认插槽,具名插槽 -->
<Test>
<div>
这里有个插槽
</div>
<template #a>
<div>插槽a</div>
</template>
<!-- 具名插槽 v-sot指令 可以简写成# -->
<!-- <template v-slot:b> -->
<template #b>
<div>插槽b</div>
</template>
</Test>
</template>
<script setup lang = 'ts'>
import Test from './Test.vue'
import Test1 from './Test1.vue'
import { ref } from 'vue'
// 插槽: 默认插槽 具名插槽 作用域插槽
// 作用域插槽:可以传递数据的插槽,子组件可以将数据传递给父组件,
// 父组件决定这些数据是以何种的结构或者外观展示
let todos = ref([{ id: 1, title: '上班', done: true }, { id: 1, title: '摸鱼', done: false }, { id: 1, title: '打游戏', done: true }])
</script>
Test组件:
<template>
<h2>子组件默认插槽</h2>
<slot></slot>
<h2>子组件默认插槽</h2>
<h2>子组件具名插槽a</h2>
<slot name="a"></slot>
<h2>子组件具名插槽a</h2>
<h2>子组件具名插槽b</h2>
<slot name="b"></slot>
<h2>子组件具名插槽b</h2>
</template>
Test1组件:
<template>
<div>
<h1>作用域插槽</h1>
<ul>
<li v-for="(item, index) in todos" :key="item.id">
<!-- 作用域插槽:可以将数据回传给父组件 -->
<slot :$row="item" :$index="index"></slot>
</li>
</ul>
</div>
</template>
<script setup lang = 'ts'>
defineProps(['todos'])
</script>