vue3中组件通信方式有以下几种:
- props
- custom event 自定义事件
- event bus 全局事件总线 $bus(mitt)
- v-model
- userAttrs
- ref与$parent
- provide与inject
- pinia
- slot 插槽
现在用具体的Demo来实现组件通信
注:此文章示例基于 vue3+ts+vite
-
props
新建Father.vue和Children.vue两个文件 流程是:父(Father.vue)传子(Children.vue)数据 : name和age 以下是父(Father.vue)文件的内容
<template>
<!-- 传递非字符串类型需要加v-bind 简写 冒号 -->
<Children :name="name" age="66" :personal="personal"></Children>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import Children from './Children.vue';
const name = 'zs';
const personal = reactive([{ bh: '001', xdmc: '寻' }]);
</script>
以下是Children.vue的内容
<template>
<div>{{ name }}:{{ age }}</div>
</template>
<script setup lang="ts">
// defineProps 是vue3 提供方法,不需要引入直接使用
//defineProps<{ name: string; age: number }>();
const props = defineProps(['name', 'age']);
// 需要使用到defineProps方法去接收父组件传过来的数据。
// props :只读
console.log(props.name);
</script>
-
custom event 自定义事件
>在vue框架中事件分为两种:一种是原生的DOM事件,另外一种是自定义事件. 原生DOM事件可以让用户与网页进行交互,比如:click、change、mouseeter、mouseleave... 自定义事件可以实现子组件给父组件传递数据。
以下是父组件 Father.vue的内容
<template>
<Children @click="handler"></Children>
</template>
<script setup lang="ts">
import Children from './Children.vue';
// 点击子组件任意位置都能触发
const handler = () => {
console.log('点击了...');
};
</script>
自定义事件实现子组件给父组件传递数据
利用 defineEmits方法返回函数触发自定义事件
defineEmits方法不需要引入直接使用
以下是Father.vue中的内容
<template>
<div class="father">
<Children @setHandler="handler"></Children>
</div>
</template>
<script setup lang="ts">
import Children from './Children.vue';
// 接收从子组件传过来的值
const handler = (params1: any, params2: any) => {
console.log(params1, params2);
};
</script>
<style scoped>
.father {
width: 800px;
height: 800px;
background-color: green;
}
</style>
以下是子组件children.vue里的内容
<template>
<div class="children">
<el-button @click="setValue">点击我传给父组件值</el-button>
</div>
</template>
<script setup lang="ts">
const $emit = defineEmits(['setHandler']);
const setValue = () => {
$emit('setHandler', 'zs', '18');
};
</script>
<style scoped>
.children {
width: 200px;
height: 200px;
z-index: 99;
background-color: red;
}
</style>
- event bus 全局事件总线 $bus(mitt)
1.安装插件 npm install mitt
2.新建 bus.ts文件引入mitt
3.新建文件 father.vue children1.vue children2.vue
4.流程是:兄弟之间传值 children1 传值给children2
以下是bus文件 bus.ts
import mitt from 'mitt';
const $bus = mitt();
export default $bus;
以下father.vue文件的内容
<template>
<div class="father">
<Children1></Children1>
<Children2></Children2>
</div>
</template>
<script setup lang="ts">
import Children1 from './Children1.vue';
import Children2 from './Children2.vue';
</script>
<style scoped>
.father {
width: 400px;
height: 400px;
background-color: green;
}
</style>
以下children1.vue文件的内容
<template>
<div class="children">
<el-button @click="handler">点击送给兄弟值</el-button>
</div>
</template>
<script setup lang="ts">
import $bus from '../bus/index';
const handler = () => {
$bus.emit('setValue', '兄弟来吃饭');
};
</script>
<style scoped>
.children {
width: 200px;
height: 200px;
z-index: 99;
background-color: red;
}
</style>
以下children2.vue文件的内容
<template>
<div class="children">{{ childrenValue }}</div>
</template>
<script setup lang="ts">
import $bus from '../bus/index';
import { onMounted, ref } from 'vue';
const childrenValue = ref();
// 组件挂载完毕的时候,当前组件绑定一个事件,接收将来兄弟组件传递的数据。
onMounted(() => {
// 第一个参数:即为事件类型,第二个参数:即为事件回调
$bus.on('setValue', (item) => {
childrenValue.value = item;
});
});
</script>
<style scoped>
.children {
width: 200px;
height: 200px;
z-index: 99;
color: #ffff;
background-color: black;
}
</style>
- v-model
1.收集表单数据,实现数据双向绑定
2.实现父子组件数据同步
3.v-model在组件身上使用 相当于给子组件props[modelValue=1000];相当于给子组件绑定自定义事件
4.同时绑定多个v-model
以下father.vue文件的内容
<template>
<div class="father">
{{ money }}
<el-button @click="money += 1">父组件更改mony</el-button>
<Children1 v-model:money="money" v-model:pageNo='pageNo' v-model:pageSize='pageSize'></Children1>
<Children2></Children2>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Children1 from './Children1.vue';
import Children2 from './Children2.vue';
const money = ref(0);
</script>
<style scoped>
.father {
width: 400px;
height: 400px;
background-color: green;
}
</style>
以下children1.vue文件的内容
<template>
<div class="children">
<el-button @click="handler">点击子组件同步父组件的值</el-button>
</div>
</template>
<script setup lang="ts">
const props = defineProps(['money']);
// 用v-model 绑定 会自动生成 update:money
const $emit = defineEmits(['update:money']);
const handler = () => {
$emit('update:money', props.money + 100.5);
};
</script>
<style scoped>
.children {
width: 200px;
height: 200px;
z-index: 99;
background-color: red;
}
</style>
-
userAttrs
vue3框架提供了一个方法useAttrs方法,它可以获取到组件身上的属性与事件 props 与 useAttrs 方法都可以获取父组件传递过来的属性与属性值 但是props接收了useAttrs方法就获取不到了
以下father.vue文件的内容
<template>
<div class="father">
<Children
type="primary"
size="small"
title="编辑按钮"
@click="onClickGet"
></Children>
</div>
</template>
<script setup lang="ts">
import Children from './Children.vue';
const onClickGet = () => {};
</script>
<style scoped>
.father {
width: 400px;
height: 400px;
background-color: green;
}
</style>
以下children.vue文件的内容
<div class="children">{{ $attrs }}</div>
</template>
<script setup lang="ts">
import { useAttrs } from 'vue';
// props 与 useAttrs 方法都可以获取父组件传递过来的属性与属性值
//但是props接收了useAttrs方法就获取不到了
const props = defineProps(['title']);
let $attrs = useAttrs();
console.log($attrs);
</script>
<style scoped>
.children {
width: 200px;
height: 200px;
z-index: 99;
background-color: red;
}
</style>
-
ref与$parent
ref可以获取真实的DOM节点,可以获取到子组件实例 组件内部数据对外关闭的,别人不能访问 如果想让外部访问需要通过defineExpose方法对外暴露 $parent 可以在子组件内部获取到父组件的实例
以下是father.vue中的内容
<template>
<div class="father">
<Children ref="sonRef"></Children>
<el-button @click="updateSon">点击更改子组件的数据</el-button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Children from './Children.vue';
const sonRef = ref();
const updateSon = () => {
sonRef.value.money = 2020;
};
</script>
<style scoped>
.father {
width: 400px;
height: 400px;
background-color: green;
}
</style>
以下children.vue文件的内容
<template>
<div class="children">{{ money }}</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const money = ref(1000);
// 组件内部数据对外关闭的,别人不能访问
// 如果想让外部访问需要通过defineExpose方法对外暴露
defineExpose({ money });
</script>
<style scoped>
.children {
width: 200px;
height: 200px;
z-index: 99;
background-color: red;
}
</style>
-
provide与inject
vue提供了provide(提供)与inject(注入),可以是爱你隔辈组件传递数据 数据指向同一个对象 所以孙辈修改数据,祖先数也同步更新 流程: 创建ancestors.vue(祖先) father.vue(儿子) son.vue(孙子) 文件 祖先给后代 孙子组件传递数据。
<template>
<div class="father">
<Children ref="sonRef"></Children>
</div>
</template>
<script setup lang="ts">
import { provide } from 'vue';
import Children from './Children.vue';
// 祖先组件给后代组件提供数据
// 两个参数:第一个参数是提供的数据key
// 第二个参数:祖先组件提供数据
provide('Token', '乖孙儿给你糖吃');
</script>
<style scoped>
.father {
width: 400px;
height: 400px;
background-color: green;
}
</style>
</style>
以下son.vue文件的内容
<template>
<div class="children">{{ gr }}:谢谢爷爷</div>
</template>
<script setup lang="ts">
import { inject } from 'vue';
// 后代元素 不论是儿子还是孙子 都可以接收
// 注入祖先组件提供数据
// 需要参数:即为 祖先提供数据的key
const gr = inject('Token');
</script>
<style scoped>
.children {
width: 200px;
height: 200px;
z-index: 99;
background-color: red;
}
</style>
-
pinia
1. vuex 1.1 集中式管理状态容器,可以实现任意组件之间通信 2.2 核心概念:state、mutation、actions、getters、moudules 2.pinia 2.1 集中式管理状态容器,可以实现任意组件之间通信 2.2 核心概念:state、actions、getters 2.3 pinia 选择式API 和组合式API
//以下是pinia选择式API
// 定义 info 小仓库
import { defineStore } from 'pinia';
// 第一个仓库:小仓库名字 第二个参数:小仓库配置对象
// defineStore 方法执行会返回一个函数,函数作用就是让组件可以获取到仓库数据
let userInfoStore = 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 userInfoStore;
// 组件使用
<div>{{infoStore.count}}</div>
<div>{{infoStore.total}}</div>
import userInforStore from '../../store/modules/info';
// 获取小仓库对象
let infoStore=useInfoStore();
// 修改数据方法
const updateCount=()=>{
infoStore.updateNum(66,77);
}
// 以下是pinia 组合式API
// 定义组合式API仓库
import { defineStore } from 'pinia';
import { ref } 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:number, next:number) => {
return prev + next;
},0)
})
return {
todos,
arr,
updateTodo() {
todos.value.push({id:4,title:'组合式API'})
}
}
})
export default useTodoStore;
-
slot 插槽
- 默认插槽
- 具名插槽
- 作用域插槽
//以下father.vue的内容
<template>
<div class="father">
<Children ref="sonRef">
<h1>我是默认插槽</h1>
</Children>
</div>
</template>
<script setup lang="ts"></script>
<style scoped>
.father {
width: 400px;
height: 400px;
background-color: green;
}
</style>
// 以下是children的内容
<template>
<div class="children">
<slot></slot>
</div>
</template>
<script setup lang="ts"></script>
<style scoped>
.children {
width: 200px;
height: 200px;
z-index: 99;
background-color: red;
}
</style>
以下是具名插槽
// 以下是fater.vue 的内容
<template>
<div class="father">
<Children ref="sonRef">
<template v-slot:header>
<h1>具名插槽---头部</h1>
</template>
<template v-slot:footer>
<h1>具名插槽---底部</h1>
</template>
</Children>
</div>
</template>
<script setup lang="ts">
import Children from './Children.vue';
</script>
<style scoped>
.father {
width: 400px;
height: 400px;
background-color: green;
}
</style>
// 以下是children.vue 的内容
<template>
<div class="children">
<slot name="header"></slot>
<slot name="footer"></slot>
</div>
</template>
<script setup lang="ts"></script>
<style scoped>
.children {
width: 200px;
height: 200px;
border: 1px solid red;
background-color: red;
}
</style>
以下是作用域插槽
可以传递数据的插槽
子组件可以将数据回传给父组件
父组件可以决定这些回传的数据是以何种结构或外观在内部去展示
//这是father.vue的内容
<template>
<div class="father">
<Children ref="sonRef" :zcb="arr">
<template v-slot="$row">
<div
:style="{
background: $row.$index % 2 == 0 ? 'red' : '#fff',
width: 200,
height: 20
}"
>
{{ $row.$row.name }}
</div>
</template>
</Children>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import Children from './Children.vue';
let arr = reactive([
{ name: '周一', value: '3顿' },
{ name: '周二', value: '4顿' },
{ name: '周三', value: '2顿' },
{ name: '周四', value: '3顿' },
{ name: '周五', value: '3顿' },
{ name: '周六', value: '3顿' },
{ name: '周日', value: '1顿' }
]);
</script>
<style scoped>
.father {
width: 400px;
height: 400px;
background-color: green;
}
</style>
// 这是children.vue 的内容
<template>
<div class="children" v-for="(item, index) in zcb" :key="item.name">
<slot :$row="item" :$index="index"></slot>
</div>
</template>
<script setup lang="ts">
defineProps(['zcb']);
</script>
<style scoped>
.children {
width: 20px;
height: 20px;
border: 1px solid red;
background-color: red;
}
</style>
</style>