props可以实现父子组件通信,在vue3中我们可以通过defineProps获取父组件传递的数据
在组件内部不需要引入defineProps方法可以直接使用
props是只读属性!!!
父页面向子组件传值
<Child info="我是曹操" :money="money">Child>
子组件获取父组件传值:方式一
let props = defineProps({
info:{
type:String,//接受的数据类型
default:'默认参数',//接受默认数据
},
money:{
type:Number,
default:0
}})
子组件获取父组件传值:方式二
let props = defineProps(["info",'money']);
在vue框架中事件分为两种:一种是原生的DOM事件,另外一种自定义事件
原生DOM事件可以让用户与网页进行交互,比如click、dbclick、change...
自定义事件可以实现子组件给父组件传递数据
示例代码
当前代码级给pre标签绑定原生DOM事件点击事件,默认会给事件回调注入event事件对象
<pre @click="handler">
大江东去浪淘尽,千古分流人物
pre>
const handler = ($event) => {
//$event即是事件对象
console.log('点击事件')
alert('点击事件')
}
回调事件传递多个参数
<button @click="handler1(1, 2, 3, 4, $event)">点击我传递多个参数button>
const handler1 = (a, b, c, d, e) => {
console.log('事件回调2', a, b, c, d, e)
}
vue2框架当中 :下边写法是自定义事件,可以通过.native修饰符变为原生Dom事件
vue3框架中下边写法其实就是原生Dom事件
vue3:原生dom事件不管放在标签身上,组件标签身上都是原生dom事件
示例代码 父组件
<Event1 @click="handler2">Event1>
const handler2 = () => {
console.log('事件回调3')
}
示例代码 子组件 Event1
<template>
<div class="son">
<p>我是子组件1</p>
<button>点击我也执行</button>
</div>
</template>
<style scoped>
.son{
width: 400px;
height: 200px;
background: skyblue;
}
</style>
实现效果
绑定自定义事件 实现子组件给父组件传递数据
示例代码 父页面
<Event2 @xxx="handler3" @click="handler4">Event2>
//事件回调--4
const handler3 = (a, b) => {
console.log('事件回调4', a, b)
}
//事件回调--5
const handler4 = (a) => {
console.log('事件回调5', a)
alert('事件回调5')
}
示例代码 子组件
使用defineEmits 进行调用父页面方法并传递参数
defineEmits 无需引入
正常说组件标签书写@click应该为原生DOM事件,但是如果子组件内部通过defineEmits定义就变为自定义事件
<template>
<div class="child">
<p>我是子组件2p>
<button @click="handler">点击我触发自定义事件xxxbutton>
<button @click="$emit('click', 'ak47')">点击我触发自定义事件clickbutton>
div>
template>
<script setup lang="ts">
//步骤一
//利用 defineEmits 方法返回函数触发自定义事件
//defineEmits 不需要引入直接使用
//此处写上click 即视为自定义事件
let $emit = defineEmits(['xxx', 'click'])
//按钮点击回调
const handler = () => {
//步骤二
//第一个参数:事件类型 第二个参数|第三个|...注入的数据
$emit('xxx', '导弹', '火箭')
}
script>
<style scoped>
.child {
width: 400px;
height: 200px;
background: pink;
}
style>
全局事件总线可以实现任意组件通信,在vue2中可以根据VM与VC关系推出全局事件总线
但是在vue3中没有Vue构造函数,也就没有Vue.prototype.以及组合式API写法没有this
想在Vue3中使用全局事件总线功能可以使用插件mitt实现
实现功能: 父组件包含 A B两个组件,实现A组件传值 B组件接值
示例代码 封装mitt方法
//创建bus文件夹(一般放在utils中),创建index.ts
//引入mitt插件:mitt一个方法,方法执行会返回bus对象
import mitt from 'mitt';
const $bus = mitt();
export default $bus;
示例代码 父页面
<template>
<div class="box">
<h1>全局事件总线$bush1>
<hr />
<div class="container">
<Child1>Child1>
<Child2>Child2>
div>
div>
template>
<script setup lang="ts">
//引入子组件
import Child1 from "./Child1.vue";
import Child2 from "./Child2.vue";
script>
<style scoped>
.box {
width: 100vw;
height: 400px;
background: yellowgreen;
}
.container{
display: flex;
justify-content: space-between;
}
style>
示例代码 Child2组件 (传值)
<template>
<div class="child2">
<h2>我是子组件2:曹丕h2>
<button @click="handler">点击我给兄弟送一台法拉利button>
div>
template>
<script setup lang="ts">
//引入$bus对象
import $bus from '../../bus'
//点击按钮回调
const handler = () => {
$bus.emit('Car', { car: '法拉利' })
}
script>
<style scoped>
.child2 {
width: 300px;
height: 300px;
background: skyblue;
}
style>
示例代码 Child1组件 (接值)
<template>
<div class="child1">
<h3>我是子组件1:曹植h3>
div>
template>
<script setup lang="ts">
import $bus from '../../bus'
//组合式API函数
import { onMounted } from 'vue'
//当组件挂载完毕的时候 当前组件绑定一个事件 接收将来兄弟组件传递的数据
onMounted(() => {
//第一个参数:事件类型 第二个参数:事件回调
$bus.on('Car', (car) => {
console.log('bus接收', car)
})
})
script>
<style scoped>
.child1 {
width: 300px;
height: 300px;
background: hotpink;
}
style>
v-model指令可是收集表单数据(数据双向绑定),除此之外它也可以实现父子组件数据同步
而v-model实指利用props[modelValue]与自定义事件[update:modelValue]实现的
props[modelValue]+[update:modelValue]
父页面代码
<Child :modelValue="money" @update:modelValue="handler">Child>
//自定义事件的回调
const handler = (value: number) => {
//接收子组件传递过来的数据
money.value = value
}
子组件代码
<template>
<div class="child">
<h3>钱数:{{ modelValue }}h3>
<button @click="handler">父子组件数据同步button>
div>
template>
<script setup lang="ts">
//接收props
let props = defineProps(['modelValue'])
let $emit = defineEmits(['update:modelValue'])
//子组件内部按钮的点击回调
const handler = () => {
//触发自定义事件
$emit('update:modelValue', props.modelValue + 1000)
}
script>
<style scoped>
.child {
width: 600px;
height: 300px;
background: skyblue;
}
style>
上诉代码中父组件 中更改为如下
<Child v-model="money">Child>
父页面
<Child1 v-model:pageNo="pageNo" v-model:pageSize="pageSize">Child1>
子组件
<template>
<div class="child2">
<h1>同时绑定多个v-modelh1>
<button @click="handler">pageNo{{ pageNo }}button>
<button @click="$emit('update:pageSize', pageSize + 10)">
pageSize{{ pageSize }}
button>
div>
template>
<script setup lang="ts">
let props = defineProps(['pageNo', 'pageSize'])
let $emit = defineEmits(['update:pageNo', 'update:pageSize'])
//第一个按钮的事件回调
const handler = () => {
$emit('update:pageNo', props.pageNo + 3)
}
script>
<style scoped>
.child2 {
width: 300px;
height: 300px;
background: hotpink;
}
style>
在Vue3中可以利用useAttrs方法获取组件的属性与事件(包含:原生DOM事件或者自定义事件)
子组件内部可以通过useAttrs方法获取组件属性与事件类似于props,可以接受父组件传递过来的属性与属性值。需要注意如果defineProps接受了某一个属性,useAttrs方法返回的对象身上就没有相应属性与属性值
v-bind绑定多个值时可简写 :="{ a: 1, b: 2 }"
父页面 代码
<template>
<div>
<h1>useAttrsh1>
<el-button type="primary" size="small" :icon="Edit">el-button>
<HintButton
type="primary"
size="small"
:icon="Edit"
title="编辑按钮"
@click="handler"
>HintButton>
div>
template>
<script setup lang="ts">
//vue3框架提供一个方法useAttrs方法,它可以获取组件身上的属性与事件!!!
//图标组件
import { Edit } from '@element-plus/icons-vue'
import HintButton from './HintButton.vue'
//按钮点击的回调
const handler = () => {
alert(12306)
}
script>
子页面代码
<template>
<div :title="title">
<el-button :="$attrs">el-button>
<h1 v-bind="{ a: 1, b: 2 }">h1>
div>
template>
<script setup lang="ts">
//接收父组件传递过来的数据
// defineProps(['type', 'size', 'icon'])
//引入useAttrs方法:获取组件标签身上属性与事件
import { useAttrs } from 'vue'
console.log(useAttrs)
let $attrs = useAttrs()
//万一用props接受title
let props = defineProps(['title'])
console.log(props) //有title属性
console.log($attrs) //没有title属性 props优先级更高
//props和useAttrs方法都可以获取父组件传递过来的属性与属性值
//但是props接受了useAttrs方法就获取不到了
script>
<style scoped>style>
在父组件内部通过ref获取子组件实例VC,那么子组件内部的方法与响应式数据父组件可以使用的
$parent:可以在子组件内部获取到父组件的实例
实现以下效果一
父页面 代码 组件绑定ref
<template>
<div class="box">
<h1>我是父亲曹操:{{ money }}h1>
<button @click="handler()">找我的儿子曹植借10元button>
<hr />
<Son ref="son">Son>
div>
template>
<script setup lang="ts">
//ref:可以获取真实的DOM节点,可以获取到子组件实例VC
//$parent:可以在子组件内部获取到父组件的实例
//引入子组件
import Son from './Son.vue'
import { ref } from 'vue'
//父组件钱数
let money = ref(100000000)
//获取子组件的实例
let son = ref()
//父组件内部按钮点击回调
const handler = () => {
money.value += 10
console.log(son.value.money)
//儿子的钱数-10
son.value.money -= 10
//调用子组件的方法
son.value.fly()
}
script>
<style scoped>
.box {
width: 100vw;
height: 500px;
background: skyblue;
}
style>
子组件代码 暴漏 变量
<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>
<style scoped>
.son {
width: 300px;
height: 200px;
background: cyan;
}
style>
实现效果
点击子组件按钮,子组件金额-10000 父组件金额+10000
父页面 代码 暴漏变量
<template>
<div class="box">
<h1>我是父亲曹操:{{ money }}h1>
<hr />
<Dau>Dau>
div>
template>
<script setup lang="ts">
//ref:可以获取真实的DOM节点,可以获取到子组件实例VC
//$parent:可以在子组件内部获取到父组件的实例
import Dau from './Daughter.vue'
import { ref } from 'vue'
//父组件钱数
let money = ref(100000000)
//对外暴漏
defineExpose({
money,
})
script>
<style scoped>
.box {
width: 100vw;
height: 500px;
background: skyblue;
}
style>
子组件示例代码 $parent
<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: any) => {
console.log($parent)
money.value -= 10000
$parent.money += 10000
}
script>
<style scoped>
.dau {
width: 300px;
height: 300px;
background: hotpink;
}
style>
vue3提供两个方法provide(提供)与inject(注入),可以实现隔辈组件传递参数
provide方法用于提供数据,此方法执需要传递两个参数,分别提供数据的key与提供数据value
<script setup lang="ts">
import {provide} from 'vue'
provide('token','admin_token');
</script>
后代组件可以通过inject方法获取数据,通过key获取存储的数值
<script setup lang="ts">
import {inject} from 'vue'
let token = inject('token');
</script>
示例场景
-实现功能 父页面包含子组件 子组件中又包含孙子组件,父页面和孙子组件互相传值
父页面 代码
<template>
<div class="box">
<h1>Provide与Inject{{ car }}h1>
<hr />
<Child>Child>
div>
template>
<script setup lang="ts">
import Child from './Child.vue'
//vue3提供provide(提供)与inject(注入),可以实现隔辈组件传递数据
import { ref, provide } from 'vue'
let car = ref('法拉利')
//祖先组件给后代组件 提供数据
//provide参数1 提供的数据key
//provide参数2 提供的数据value
provide('Token', car)
script>
<style scoped>
.box {
width: 100vw;
height: 600px;
background: skyblue;
}
style>
子组件代码
<template>
<div class="child">
<h1>我是子组件1h1>
<Child>Child>
div>
template>
<script setup lang="ts">
import Child from './GrandChild.vue';
script>
<style scoped>
.child{
width: 300px;
height: 400px;
background: yellowgreen;
}
style>
孙子组件 代码
<template>
<div class="child1">
<h1>孙子组件h1>
<p>{{ car }}p>
<button @click="updateCar">更新数据button>
div>
template>
<script setup lang="ts">
import { inject } from 'vue'
//注入祖先组件提供的数据
//需要参数:即为祖先提供数据的key
let car = inject('Token')
console.log(car)
const updateCar = () => {
car.value = '自行车'
}
script>
<style scoped>
.child1 {
width: 200px;
height: 200px;
background: red;
}
style>
pinia也是集中式管理状态容器,类似于vuex。
核心概念 state actions getters
pinia写法 :选择式api 组合式api
创建store文件夹=>index.ts 代码如下
//创建大仓库
import { createPinia } from 'pinia';
//createPinia方法可以用于创建大仓库
let store = createPinia();
//对外暴露,安装仓库
export default store;
在main.ts中统一挂载
//引入仓库
import store from './store'
// 创建app
const app = createApp(App)
app.use(store)
父页面代码
<template>
<div class="box">
<h1>piniah1>
<div class="container">
<Child>Child>
<Child1>Child1>
div>
div>
template>
<script setup lang="ts">
import Child from './Child.vue'
import Child1 from './Child1.vue'
//vuex:集中式管理状态容器 可以实现任意组件之间的通信
//核心概念:state mutations actions getters modules
//pinia:集中式管理状态容器 可以实现任意组件之间的通信
//核心概念 state actions getters
//pinia写法 :选择式api 组合式api
script>
<style scoped>
.box {
width: 600px;
height: 400px;
background: skyblue;
}
.container {
display: flex;
}
style>
在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;
子组件1 代码 child
<template>
<div class="child">
<h1>{{ infoStore.count }}---{{infoStore.total}}h1>
<button @click="updateCount">点击我修改仓库数据button>
div>
template>
<script setup lang="ts">
import useInfoStore from "../../store/modules/info";
//获取小仓库对象
let infoStore = useInfoStore();
console.log(infoStore);
//修改数据方法
const updateCount = () => {
//仓库调用自身的方法去修改仓库的数据
infoStore.updateNum(66,77);
};
script>
<style scoped>
.child {
width: 200px;
height: 200px;
background: yellowgreen;
}
style>
在store文件夹下=>创建modules文件夹=>创建todo.ts
//定义组合式API仓库
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;
子组件2 代码 child1
<template>
<div class="child1">
{{ infoStore.count }}
<p @click="updateTodo">{{ todoStore.arr }}{{todoStore.total}}</p>
</div>
</template>
<script setup lang="ts">
import useInfoStore from "../../store/modules/info";
//获取小仓库对象
let infoStore = useInfoStore();
//引入组合式API函数仓库
import useTodoStore from "../../store/modules/todo";
let todoStore = useTodoStore();
//点击p段落去修改仓库的数据
const updateTodo = () => {
todoStore.updateTodo();
};
</script>
<style scoped>
.child1 {
width: 200px;
height: 200px;
background: hotpink;
}
</style>
插槽:默认插槽、具名插槽、作用域插槽可以实现父子组件通信
在子组件内部的模板中书写slot全局组件标签
<template>
<div>
<slot>slot>
div>
template>
<script setup lang="ts">
script>
<style scoped>
style>
在父组件内部提供结构:Todo即为子组件,在父组件内部使用的时候,在双标签内部书写结构传递给子组件
注意开发项目的时候默认插槽一般只有一个
<Todo>
<h1>我是默认插槽填充的结构h1>
Todo>
插槽带有名字在组件内部留多个指定名字的插槽
下面是一个子组件内部,模板中留两个插槽
<template>
<div>
<h1>todoh1>
<slot name="a">slot>
<slot name="b">slot>
div>
template>
<script setup lang="ts">
script>
<style scoped>
style>
父组件内部向指定的具名插槽传递结构。需要注意v-slot:可以替换为#
<template>
<div>
<h1>sloth1>
<Todo>
<template v-slot:a> //可以用#a替换
<div>填入组件A部分的结构div>
template>
<template v-slot:b>//可以用#b替换
<div>填入组件B部分的结构div>
template>
Todo>
div>
template>
<script setup lang="ts">
import Todo from "./Todo.vue";
script>
<style scoped>
style>
作用域插槽:可以理解为,子组件数据由父组件提供,但是子组件内部决定不了自身结构与外观(样式)
子组件Todo代码如下
<template>
<div>
<h1>todoh1>
<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>
<style scoped>
style>
父组件内部代码如下
<template>
<div>
<h1>sloth1>
<Todo :todos="todos">
<template v-slot="{$row,$index}">
<span :style="{color:$row.done?'green':'red'}">{{$row.title}}span>
template>
Todo>
div>
template>
<script setup lang="ts">
import Todo from "./Todo.vue";
import { ref } from "vue";
//父组件内部数据
let todos = ref([
{ id: 1, title: "吃饭", done: true },
{ id: 2, title: "睡觉", done: false },
{ id: 3, title: "打豆豆", done: true },
]);
script>
<style scoped>
style>