1.props
父组件引入子组件,并在其中传递相关数据
<template>
<div class="box">
<h1>props:我是父组件曹操</h1>
<hr />
<Child info="我是曹操" :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>
子组件用defineProps进行接收
<template>
<div class="son">
<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','money']); //数组|对象写法都可以
</script>
props数据只读,模板中可省略对象名,直接写属性。而script中必须用**.属性
2.自定义事件
2.1.原生dom事件
模板中写一行数据,并在标签中写下点击事件handler
<pre @click="handler">
我是祖国的老花骨朵
</pre>
向事件回调注入事件对象
<script setup lang="ts">
//事件回调--1
const handler = (event)=>{
//event即为事件对象
console.log(event);
}
</script>
也可以在其中传递多个参数
模板中
<button @click="handler1(1,2,3,$event)">点击我传递多个参数</button>
事件回调中进行数据接收
<script setup lang="ts">
//事件回调--2
const handler1 = (a,b,c,$event)=>{
console.log(a,b,c,$event)
}
</script>
2.2.自定义事件
引入子组件,并在子组件中加入点击事件,其中vue2与vue3对其事件定义不一致
<!--
vue2框架当中:这种写法自定义事件,可以通过.native修饰符变为原生DOM事件
vue3框架下面写法其实即为原生DOM事件
vue3:原生的DOM事件不管是放在标签身上、组件标签身上都是原生DOM事件
-->
<Event1 @click="handler2"></Event1>
<script setup lang="ts">
//引入子组件
import Event1 from './Event1.vue';
//事件回调---3
const handler2 = ()=>{
console.log(123);
}
</script>
其中绑定的事件是给子组件的根节点绑定的
父组件中,子组件绑定自定义事件
<!-- 绑定自定义事件xxx:实现子组件给父组件传递数据 -->
<Event2 @xxx="handler3" @click="handler4"></Event2>
<script setup lang="ts">
//引入子组件
import Event1 from './Event1.vue';
//事件回调---4
const handler3 = (param1,param2)=>{
console.log(param1,param2);
}
//事件回调--5
const handler4 = (param1,param2)=>{
console.log(param1,param2);
}
</script>
子组件中
<template>
<div class="child">
<p>我是子组件2</p>
<button @click="handler">点击我触发自定义事件xxx</button>
<button @click="$emit('click','AK47','J20')">点击我触发自定义事件click</button>
</div>
</template>
<script setup lang="ts">
//利用defineEmits方法返回函数触发自定义事件
//defineEmits方法不需要引入直接使用
let $emit = defineEmits(['xxx','click']);
//按钮点击回调
const handler = () => {
//第一个参数:事件类型 第二个|三个|N参数即为注入数据
$emit('xxx','东风导弹','航母');
};
</script>
利用defineEmits方法返回函数触发自定义事件
defineEmits方法不需要引入直接使用
如:
let $emit = defineEmits(['xxx','click']);
按钮点击回调中第一个参数:事件类型 第二个|三个|N参数即为注入数据
$emit('xxx','东风导弹','航母');
其中,父组件handler4事件原为原生dom事件,但是子组件在用defineEmits引入点击事件后vue3认为点击事件已成为自定义事件,所以要想handler4事件执行,必须把事件回调重新写入,同子组件handler一样写个事件回调,或者直接把$emit('click','AK47','J20')作为事件click后的属性值。
3.全局事件总线
全局事件总线可以实现任意组件通信,在vue2中可以根据VM与VC关系推出全局事件总线。
但是在vue3中没有Vue构造函数,也就没有Vue.prototype.以及组合式API写法没有this,
那么在Vue3想实现全局事件的总线功能就有点不现实啦,如果想在Vue3中使用全局事件总线功能
可以使用插件mitt实现。
mitt:官网地址:https://www.npmjs.com/package/mitt
$ npm install --save mitt
//using ES6 modules
import mitt from 'mitt' //方法在执行会返回bus对象
//usage
const emitter = mitt()
// listen to an event
emitter.on('foo', e => console.log('foo', e) )
// listen to all events
emitter.on('*', (type, e) => console.log(type, e) )
// fire an event
emitter.emit('foo', { a: 'b' })
// clearing all events
emitter.all.clear()
// working with handler references:
function onFoo() {}
emitter.on('foo', onFoo) // listen
emitter.off('foo', onFoo) // unlisten
以上是官网查询的用法。可以看出其有着欧on和emit两种方法
我们可以在src文件夹下建立bus文件夹,再建立index.ts文档
//引入mitt插件:mitt一个方法,方法执行会返回bus对象
import mitt from 'mitt';
const $bus = mitt();
export default $bus;
需要绑定接收数据的组件内引用
<script setup lang="ts">
import $bus from "../../bus";
//组合式API函数
import { onMounted } from "vue";
//组件挂载完毕的时候,当前组件绑定一个事件,接受将来兄弟组件传递的数据
onMounted(() => {
//第一个参数:即为事件类型 第二个参数:即为事件回调
$bus.on("car", (car) => {
console.log(car);
});
});
</script>
传递数据的组件内,在事件回调中用emit先写所需触发的事件,再写数据对象。
<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>
4.v-model
v-model指令:收集表单数据,数据双向绑定
v-model也可以实现组件之间的通信,实现父子组件数据同步的业务
父亲给子组件数据 props
子组件给父组件数据 自定义事件
父组件内引入子组件,并绑定一个数据
<template>
<div>
<h1>v-model:钱数{{ money }}</h1>
<input type="text" v-model="info" />
<hr />
<!-- props:父亲给儿子数据 -->
<Child :modelValue="money" @update:modelValue="handler"></Child>
<!--
@*中:可换为其他数值字母
-->
<hr />
</div>
</template>
<script setup lang="ts">
//v-model指令:收集表单数据,数据双向绑定
//v-model也可以实现组件之间的通信,实现父子组件数据同步的业务
//父亲给子组件数据 props
//子组件给父组件数据 自定义事件
//引入子组件
import Child from "./Child.vue";
let info = ref("");
//父组件的数据钱数
let money = ref(10000);
//自定义事件的回调
const handler = (num) => {
//将来接受子组件传递过来的数据
money.value = num;
};
</script>
子组件内用defineProps接收数据
<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>
父组件内子组件绑定自定义事件。当子组件内绑定数据更新事件时,数据传向父组件。父组件若要实现数据增减同步改变的时候,其事件回调中需要接受传来的数据,
当然,v-model再组件身上使用时
第一:相当有给子组件传递props[modelValue] = 10000
第二:相当于给子组件绑定自定义事件update:modelValue
<Child v-model="money"></Child>
v-model还有一种语法
就是在父组件定义pageNo,pageSize两个数据,并且在其引入的子组件内,用v-model绑定一个单独属性名,属性值为所定义两个数据
<template>
<Child1 v-model:pageNo="pageNo" v-model:pageSize="pageSize"></Child1>
</template>
<script setup lang="ts">
//v-model指令:收集表单数据,数据双向绑定
//v-model也可以实现组件之间的通信,实现父子组件数据同步的业务
//父亲给子组件数据 props
//子组件给父组件数据 自定义事件
//引入子组件
import Child1 from "./Child1.vue";
import { ref } from "vue";
//父亲的数据
let pageNo = ref(1);
let pageSize = ref(3);
</script>
子组件内
<template>
<div class="child2">
<h1>同时绑定多个v-model</h1>
<button @click="handler">pageNo{{ pageNo }}</button>
<button @click="$emit('update:pageSize', pageSize + 4)">
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>
'update:属性名'是在v-model进行绑定数据后,自动创建的自定义事件,若想要对数据进行操作,可defineEmits接收相应的自定义事件,并在所需标签内绑定事件,在事件回调内写入相关操作。
5.useAttrs
vue3框架提供一个方法useAttrs方法,它可以获取组件身上的属性与事件!!!
类似于props,但优先级相对较低
在Vue3中可以利用useAttrs方法获取组件的属性与事件(包含:原生DOM事件或者自定义事件),次函数功能类似于Vue2框架中$attrs属性与$listeners方法。
父组件内(app组件内已经安装引入了elment ui)
<template>
<div>
<h1>useAttrs</h1>
<el-button type="primary" size="small" :icon="Edit"></el-button>
<!-- 自定义组件 -->
<HintButton type="primary" size="small" :icon="Edit"
title="编辑按钮" @click="handler" @xxx="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 = ()=>{
alert(12306);
}
</script>
子组件用props可正常接收数据,也可通过useAttrs
<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>
其得到代理对象,如果用props接收数据,则useAttrs上就没有该数据,props优先级更高
6.ref与$parent
ref:可以获取真实的DOM节点,可以获取到子组件实例VC
$parent:可以在子组件内部获取到父组件的实例
父组件
<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'
//女儿组件,在使用$parent时会进行导入,这里只是提前导入了
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>
子组件
<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>
父组件中引入的子组件,用ref='son',并且在script标签内获取子组件的实例let son = ref();
但是,对于组件来说,他的数据的默认关闭的不对外暴露
所以,就算父组件拿到了子组件实例,却拿不到子组件的数据
若需要让外部访问则应该用defineExpose方法对外暴露
数据与方法在对外暴露都可以使用
女儿组件
<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>
女儿组件在点击按钮时注入$parent,这可以获取到其父组件的实例
当然,父组件还是得对外暴露他的数据,不然女儿组件无法拿到相关的数据
7.provide与inject
vue3提供provide(提供)与inject(注入),可以实现隔辈组件传递数据
祖先组件给后代组件提供数据有两个参数:
第一个参数就是提供的数据key
第二个参数:祖先组件提供数据
祖先提供数据后儿子,孙子都可以进行使用。
父组件
<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("法拉利");
//祖先组件给后代组件提供数据
//两个参数:第一个参数就是提供的数据key
//第二个参数:祖先组件提供数据
provide("TOKEN", car);
</script>
子组件
<template>
<div class="child">
<h1>我是子组件1</h1>
<Child></Child>
</div>
</template>
<script setup lang="ts">
import Child from './GrandChild.vue';
</script>
孙子组件
<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');
const updateCar = ()=>{
car.value = '自行车';
}
</script>
祖先用$parent('自定义key',数据)将数据提供,
子代组件用引入inject,将数据注入组件内。
注入时,需要的是祖先提供的自定义key值,然后其会返回祖先提供的数据。
子代组件可以通过事件进行修改父组件所传过来的数据
8.pinia
vuex:集中式管理状态容器,可以实现任意组件之间通信!!!
核心概念:state、mutations、actions、getters、modules
pinia:集中式管理状态容器,可以实现任意组件之间通信!!!
核心概念:state、actions、getters
pinia写法:选择器API、组合式API
父组件
<template>
<div class="box">
<h1>pinia</h1>
<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>
子组件
<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>
子组件1
<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>
src文件夹下建立一个仓库文件夹store
index.ts
//创建大仓库
import { createPinia } from 'pinia';
//createPinia方法可以用于创建大仓库
let store = createPinia();
//对外暴露,安装仓库
export default store;
当然必须得到入口文件main.ts引入这个仓库
//引入仓库
import store from './store'
再建立两个小仓库
info.ts(选择式api)
//定义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;
todo.ts(组合式api)
//定义组合式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;
用defineStore方法用于定义小仓库,其需要传递两个参数:
第一个仓库:小仓库名字
第二个参数:小仓库配置对象
defineStore方法执行会返回一个函数,函数作用就是让组件可以获取到仓库数据
函数写法,return返回的对象才是提供的数据
子组件引入仓库,接收数据所返回的对象(想用就导入仓库,一旦某一组件修改了所接受的仓库数据,那么仓库数据也被修改,所导入仓库的组件数据也会被修改)
可直接修改数据,做到组件间通信,也可用所接受仓库对象上的方法$patch进行数据修改
const updateCount = () =>{
infoStore.$patch({
count:1111
})
console.log(infoStore);
};
也可在仓库中定义方法,组件导入仓库后直接使用进行修改所需数据,类似于$patch
(注意:函数没有context上下文对象)
可在定义的方法内注入一些参数
仓库
actions:{
updateNum(a:number,b:number){
console.log(a,b);
this.count++;
}
},
组件内如上(子组件)代码,添加了两个参数
仓库里用this,返回该仓库对象,可用this里的方法进行修改数据
(没有commit、没有mutations去修改数据)\
todo.ts是采用组合式api的写法,定义小仓库的方法和上面info.ts一样,引用vue下的ref函数或者reactive函数,使数据成为响应式(得在return外创建数据,实现响应式),组件导入数据依旧和info.ts一样
return里面创建相关方法,若要计算方法,则导入vue中的computed,在return外进行相关计算
主要是运用的vue中组合式api的函数
9.插槽
9.1.默认插槽
SlotTest.vue
<template>
<div>
<h1>slot</h1>
<Test>
<div>
<pre>大江东去浪淘尽,千古分流人物</pre>
</div>
</Test>
</div>
</template>
<script setup lang="ts">
import Test from "./Test.vue";
//插槽:默认插槽、具名插槽、作用域插槽
//作用域插槽:就是可以传递数据的插槽,子组件可以讲数据回传给父组件,父组件可以决定这些回传的
//数据是以何种结构或者外观在子组件内部去展示!!!
</script>
Test.vue
<template>
<div class="box">
<h1>我是子组件默认插槽</h1>
<!-- 默认插槽 -->
<slot></slot>
</div>
</template>
子组件内用<slot></slot>标签,设置所需插入页面的位置,父组件在引入的子组件内加入所要插入子组件的内容和格式(标签所带格式都会保存)
9.2.具名插槽
SlotTest.vue
<template>
<div>
<h1>slot</h1>
<Test>
<!-- 具名插槽填充a -->
<template #a>
<div>我是填充具名插槽a位置结构</div>
</template>
<!-- 具名插槽填充b v-slot指令可以简化为# -->
<template #b>
<div>我是填充具名插槽b位置结构</div>
</template>
</Test>
</div>
</template>
<script setup lang="ts">
import Test from "./Test.vue";
//插槽:默认插槽、具名插槽、作用域插槽
//作用域插槽:就是可以传递数据的插槽,子组件可以讲数据回传给父组件,父组件可以决定这些回传的
//数据是以何种结构或者外观在子组件内部去展示!!!
</script>
Test.vue
<template>
<div class="box">
<h1>具名插槽填充数据</h1>
<slot name="a"></slot>
<h1>具名插槽填充数据</h1>
<h1>具名插槽填充数据</h1>
<slot name="b"></slot>
<h1>具名插槽填充数据</h1>
</div>
</template>
子组件对<slot name='a'></slot>的name属性值进行修改,父组件内所导入的子组件里,若是标签有相同的#属性值,则该标签内的所有内容都会插入子组件<slot name='属性值'></slot>相关位置内.
父组件内所导入的子组件里标签内v-slot:a'等价于 #a
9.3.作用域插槽
SlotTest.vue
<template>
<div>
<h1>slot</h1>
<Test1 :todos="todos">
<template v-slot="{ $row, $index }">
<p :style="{ color: $row.done ? 'green' : 'red' }">
{{ $row.title }}--{{ $index }}
</p>
</template>
</Test1>
</div>
</template>
<script setup lang="ts">
import Test1 from "./Test1.vue";
//插槽:默认插槽、具名插槽、作用域插槽
//作用域插槽:就是可以传递数据的插槽,子组件可以讲数据回传给父组件,父组件可以决定这些回传的
//数据是以何种结构或者外观在子组件内部去展示!!!
import { ref } from "vue";
//todos数据
let todos = ref([
{ id: 1, title: "吃饭", done: true },
{ id: 2, title: "睡觉", done: false },
{ id: 3, title: "打豆豆", done: true },
{ id: 4, title: "打游戏", done: false },
]);
</script>
Test1.vue
<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>
作用域插槽:就是可以传递数据的插槽,子组件可以讲数据回传给父组件,父组件可以决定这些回传的
数据是以何种结构或者外观在子组件内部去展示!!!
<!--作用域插槽:可以讲数据回传给父组件-->
v-for="(item, index) in todos" :key="item.id" v-for 进行遍历,关键值为其id
:$row="item" 为插槽语法,可将数据回传给父组件
:$index="index" $index为每条数据的索引值
未完待续,持续更新(仅自用笔记)