vue3组件间通信
props
- 父子组件之间通信最好的方式
// 父组件
<template>
<div class="box">
<h1>props:这里是父组件</h1>
<hr />
<Child :money="money"></Child>
</div>
</template>
<script setup lang="ts">
//props:可以实现父子组件通信,props数据还是只读的!!!
import Child from "./Child.vue";
import { ref } from "vue";
let money = ref(10000);
</script>
// 子组件
<template>
<div class="son">
<h1>我是子组件</h1>
<p>{{props.money}}</p>
<!--props可以省略前面的名字--->
<p>{{money}}</p>
<button @click="updateProps">修改props数据</button>
</div>
</template>
<script setup lang="ts">
//需要使用到defineProps方法去接受父组件传递过来的数据
//defineProps是Vue3提供方法,不需要引入直接使用
let props = defineProps(['money']); //数组|对象写法都可以
//按钮点击的回调
const updateProps = ()=>{
// props.money+=10; props:只读的
console.log(props)
}
</script>
自定义事件
- 父子组件通信
// 父组件
<template>
<div>
<button @click="handler1(1,2,3,$event)">点击我传递多个参数</button>
<hr>
<!--
vue2框架当中:这种写法自定义事件,可以通过.native修饰符变为原生DOM事件
vue3框架下面写法其实即为原生DOM事件
vue3:原生的DOM事件不管是放在标签身上、组件标签身上都是原生DOM事件
-->
<Event1 @click="handler2"></Event1>
<hr>
<!-- 绑定自定义事件xxx:实现子组件给父组件传递数据 -->
<Event2 @xxx="handler3" @click="handler4"></Event2>
</div>
</template>
<script setup lang="ts">
//引入子组件
import Event1 from './Event1.vue';
//引入子组件
import Event2 from './Event2.vue';
//事件回调--1
const handler = (event)=>{
//event即为事件对象
console.log(event);
}
//事件回调--2
const handler1 = (a,b,c,$event)=>{
console.log(a,b,c,$event)
}
//事件回调---3
const handler2 = ()=>{
console.log(123);
}
//事件回调---4
const handler3 = (param1,param2)=>{
console.log(param1,param2);
}
</script>
// 子组件2
<template>
<div class="child">
<p>我是子组件2</p>
<button @click="handler">点击我触发自定义事件xxx</button>
<button @click="$emit('click','111','222')">点击我触发自定义事件click</button>
</div>
</template>
<script setup lang="ts">
//利用defineEmits方法返回函数触发自定义事件
//defineEmits方法不需要引入直接使用
let $emit = defineEmits(['xxx','click']);
//按钮点击回调
const handler = () => {
//第一个参数:事件类型 第二个|三个|N参数即为注入数据
$emit('xxx','333','444');
};
</script>
全局事件总线
- 任意组件间通信
注意:全局事件总线中 $on 比 $emit 优先执行,使用完全局事件总线后一定要注意销毁,否则会发生内存泄漏
// 使用mitt,先封装一个$bus
// bus
//引入mitt插件:mitt一个方法,方法执行会返回bus对象
import mitt from 'mitt';
const $bus = mitt();
export default $bus;
// 组件1
<template>
<div class="child1">
<h3>我是子组件child1</h3>
</div>
</template>
<script setup lang="ts">
import $bus from "../../bus";
//组合式API函数
import { onMounted } from "vue";
//组件挂载完毕的时候,当前组件绑定一个事件,接受将来兄弟组件传递的数据
onMounted(() => {
//第一个参数:即为事件类型 第二个参数:即为事件回调
$bus.on("child1", (str) => {
console.log(str);
});
});
</script>
// 组件2
<template>
<div class="child2">
<h2>我是子组件child2</h2>
<button @click="handler">点击给child1传值</button>
</div>
</template>
<script setup lang="ts">
//引入$bus对象
import $bus from '../../bus';
//点击按钮回调
const handler = ()=>{
$bus.emit('child1',"你好");
}
</script>
v-model
- 本质上还是使用了自定义事件
// v-model原理
// 在原生元素上
<input v-model="text" />
// 等价展开
<input
:value="text"
@input="text = $event.target.value"
/>
// 在组件上展开
<Child1
:modelValue="text"
@update:modelValue="newValue => text = newValue"
/>
// Child1
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
// 这里只能使用modleValue和upate:modleValue
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
// 指定参数名称
<Child2 v-model:text="text" />
// Child2
<script setup>
defineProps(['text'])
defineEmits(['update:text'])
</script>
// 实际上指定参数名称就是在子组件的defineEmits(['update:指定参数'])、defineProps([指定参数])
useAttrs
- 接收父组件的属性和方法,多用于组件二次封装
// 父组件
<template>
<div>
<h1>useAttrs</h1>
<!-- 自定义组件 -->
<HintButton type="primary" size="small" :icon="Edit" title="编辑按钮" @click="handler" ></HintButton>
</div>
</template>
// 子组件
<template>
<div :title="title">
<el-button :="$attrs"></el-button>
</div>
</template>
<script setup lang="ts">
//引入useAttrs方法:获取组件标签身上属性与事件
import { useAttrs } from "vue";
//此方法执行会返回一个对象,里面是传过来的一堆属性与方法
let $attrs = useAttrs();
//万一用props接受title
let props = defineProps(["title"]);
//props与useAttrs方法都可以获取父组件传递过来的属性与属性值
//但是props接受了useAttrs方法就获取不到了
console.log($attrs);
</script>
ref和$parent
- ref 可以获取真实的DOM节点,可以获取到子组件实例VC
- $parent 可以在子组件内部获取到父组件的实例
- 只能获取通过 defineExpose 暴露出来的对象
// 父组件
<template>
<div class="box">
<h1>我是父亲曹操:{{money}}</h1>
<button @click="handler">找我的儿子曹植借10元</button>
<hr>
<Son ref="son"></Son>
<hr>
<Dau></Dau>
</div>
</template>
<script setup lang="ts">
//ref:可以获取真实的DOM节点,可以获取到子组件实例VC
//$parent:可以在子组件内部获取到父组件的实例
//引入子组件
import Son from './Son.vue'
import Dau from './Daughter.vue'
import {ref} from 'vue';
//父组件钱数
let money = ref(100000000);
//获取子组件的实例
let son = ref();
//父组件内部按钮点击回调
const handler = ()=>{
money.value+=10;
//儿子钱数减去10
son.value.money-=10;
son.value.fly();
}
//对外暴露
defineExpose({
money
})
</script>
// 子组件1
<template>
<div class="son">
<h3>我是子组件:曹植{{money}}</h3>
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue';
//儿子钱数
let money = ref(666);
const fly = ()=>{
console.log('我可以飞');
}
//组件内部数据对外关闭的,别人不能访问
//如果想让外部访问需要通过defineExpose方法对外暴露
defineExpose({
money,
fly
})
</script>
// 子组件2
<template>
<div class="dau">
<h1>我是闺女曹杰{{money}}</h1>
<button @click="handler($parent)">点击我爸爸给我10000元</button>
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue';
//闺女钱数
let money = ref(999999);
//闺女按钮点击回调
const handler = ($parent)=>{
money.value+=10000;
$parent.money-=10000;
}
</script>
provide 和 inject
- 与后代组件通信,provide 注入数据,inject 接收数据
// 祖先组件
<script setup lang="ts">
import Child from "./Child.vue";
//vue3提供provide(提供)与inject(注入),可以实现隔辈组件传递数据
import { ref, provide } from "vue";
let car = ref("法拉利");
//祖先组件给后代组件提供数据
//两个参数:第一个参数就是提供的数据key
//第二个参数:祖先组件提供数据
provide("TOKEN", car);
</script>
// 孙子组件
<script setup lang="ts">
import {inject} from 'vue';
//注入祖先组件提供数据
//需要参数:即为祖先提供数据的key
let car = inject('TOKEN');
const updateCar = ()=>{
car.value = '自行车';
}
</script>
pinia
- vuex 升级版,任意组件间通信
pinia 只有三个核心概念
- state,存放数据和状态
- actions,存放数据和状态处理的方法
- getters,类似于计算属性
store/index.ts
// 创建 pinia 仓库,在 src 根目录下创建 store 文件夹,在 store 中创建 modules 文件夹存放小仓库的数据,在 store 中创建 index.ts 文件进行大仓库的创建
// index.ts
//创建大仓库
import { createPinia } from 'pinia';
//createPinia方法可以用于创建大仓库
let store = createPinia();
//对外暴露,安装仓库
export default store;
store/modules/info.ts
//定义info小仓库
import { defineStore } from "pinia";
//第一个仓库:小仓库名字 第二个参数:小仓库配置对象
//defineStore方法执行会返回一个函数,函数作用就是让组件可以获取到仓库数据
let useInfoStore = defineStore("info", {
//存储数据:state,使用箭头函数声明会自动推断属性的类型
state: () => {
return {
count: 99,
arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
},
actions: {
//注意:函数没有context上下文对象
//没有commit、没有mutations去修改数据
updateNum(a: number, b: number) {
this.count += a;
}
},
getters: {
// 数组求和
total() {
let result:any = this.arr.reduce((prev: number, next: number) => {
return prev + next;
}, 0);
return result;
}
}
});
//对外暴露方法
export default useInfoStore;
store/modules/todo.ts
//定义组合式API仓库,有点类似于自定义 hook 的感觉了
import { defineStore } from "pinia";
import { ref, computed,watch} from 'vue';
//创建小仓库
let useTodoStore = defineStore('todo', () => {
// 声明响应式变量
let todos = ref([{ id: 1, title: '吃饭' }, { id: 2, title: '睡觉' }, { id: 3, title: '打豆豆' }]);
let arr = ref([1,2,3,4,5]);
// 使用计算属性
const total = computed(() => {
return arr.value.reduce((prev, next) => {
return prev + next;
}, 0)
})
//务必要返回一个对象:属性与方法可以提供给组件使用
return {
todos,
arr,
total,
updateTodo() {
todos.value.push({ id: 4, title: '组合式API方法' });
}
}
});
export default useTodoStore;
组件1
<template>
<div class="child">
// 函数.属性获取store的属性值
<h1>{{ infoStore.count }}---{{infoStore.total}}</h1>
<button @click="updateCount">点击我修改仓库数据</button>
</div>
</template>
<script setup lang="ts">
import useInfoStore from "../../store/modules/info";
//获取小仓库对象,defineStore 返回的是一个函数,所以需要调用函数的方式来调用 store
let infoStore = useInfoStore();
console.log(infoStore);
//修改数据方法
const updateCount = () => {
//仓库调用自身的方法去修改仓库的数据,函数.方法调用方法
infoStore.updateNum(66,77);
};
</script>
插槽
- 也是父子组件通信的方式
// 组件1
<Test>
<div>
<pre>默认</pre>
</div>
<!-- 具名插槽填充a -->
<template #a>
<div>我是填充具名插槽a位置结构</div>
</template>
<!-- 具名插槽填充b v-slot指令可以简化为# -->
<template #b>
<div>我是填充具名插槽b位置结构</div>
</template>
</Test>
// 默认插槽和具名插槽
<template>
<div class="box">
<h1>我是子组件默认插槽</h1>
<!-- 默认插槽 -->
<slot></slot>
<h1>我是子组件默认插槽</h1>
<h1>具名插槽填充数据</h1>
<slot name="a"></slot>
<h1>具名插槽填充数据</h1>
<h1>具名插槽填充数据</h1>
<slot name="b"></slot>
<h1>具名插槽填充数据</h1>
</div>
</template>
// 组件2
// 根据子组件传过来的值 done 来决定展示的颜色
<Test1 :todos="todos">
<template v-slot="{ $row, $index }">
<p :style="{ color: $row.done ? 'green' : 'red' }">
{{ $row.title }}--{{ $index }}
</p>
</template>
</Test1>
// 作用域插槽
<template>
<div class="box">
<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">
//通过props接受父组件传递数据
defineProps(["todos"]);
</script>