一、组件通信
注 :学习代码见:链接: https://pan.baidu.com/s/1lq5vuyEPzIeRWo5eUXMZLA?pwd=olo8 提取码: olo8
1、defineProps
- defineProps 是vue内置的方法,不需要引入,可直接使用
- 方法里接收 数组 ,数组中依次将需要的 prop 字段放进去
- 子组件在 tempalte 中使用时
· 如果在 script 标签中将 defineProps 的返回值赋值给了变量 a, 在 template 中可以用 a.name 去使用,也可以直接使用 name
· 如果没有赋值给新的变量,那么可以在template中直接使用 name - 子组件在 script 中使用时,需要先赋值 a,再使用 a.name
parent.vue:
js
<template>
<div class="box">
父亲:{{ info }}
<children :info="info" :num="num"/>
</div>
</template>
<script setup lang="ts">
import children from "./children.vue"
import { ref } from "vue";
let info = ref("曹操")
let num = 0
</script>
children.vue:
js
<template>
<div class="box2">
孩子:曹丕 <br>
传递的props是:{{ props.info }}*******{{ props.num }} <br>
传递的props是:{{ info }}******{{ num }}
</div>
</template>
<script setup lang="ts">
//import { defineProps } from "vue";
let props = defineProps(["info", "num"])
console.log(props)
</script>
</script>
2、event 事件
parent.vue
js
<template>
<div class="box">
<p>这是父组件</p>
子组件1:<br/>
<children @click="clickEvent"></children>
子组件2: <br/>
<children2 @child2HandlerEvent1="handlerEvent1" @handlerEvent2="handlerEvent2"></children2>
</div>
</template>
<script setup lang="ts">
import children from "./children.vue"
import children2 from "./children2.vue"
/**
* vue2 中,如果要在子组件标签上触发自定义事件,需要通过 .native 修饰符变为原声的dom事件
* vue3 中,原生的dom事件不管是放在标签上,还是组件标签上,都是原生事件
* 如果是放在组件标签上时,
* 如果子组件只有一个根节点,那么这个事件就委托在了父元素中任意的节点上
* 如果子组件中有多个根节点,那么就需要子组件通过 defineEmits 各自触发事件
*/
// 子组件绑定原生事件
let clickEvent = (event) => {
console.log(event);
}
let handlerEvent1 = (a,b,c) => {
console.log(a,b,c);
}
let handlerEvent2 = () => {
console.log("handlerEvent2");
}
</script>
children.vue
js
<template>
<div class="box1">
<p>这是子组件1</p>
<button>点击触发事件</button>
</div>
</template>
<script setup lang="ts">
</script>
children2.vue
js
<template>
<div class="box1">
<p>这是子组件2</p>
<span @click="handlerEvent1">点击事件</span>
</div>
<div @click="handlerEvent2" class="box1">
<p>这是子组件2</p>
<span>点击事件</span>
</div>
</template>
<script setup lang="ts">
//利用 defineEmits 方法返回函数触发自定义事件
//defineEmits 方法不需引入,可以直接使用,接收一个数组,数组里面存放的是父组件中定义的方法名
let $emit = defineEmits(["child2HandlerEvent1", "handlerEvent2"])
const handlerEvent1 = () => {
//第一个参数是父组件定义的方法名,后面依次是需要传递的参数
$emit("child2HandlerEvent1", 1,2,3)
}
const handlerEvent2 = () => {
$emit("handlerEvent2")
}
</script>
3、全局事件总线-mitt
mitt:https://www.npmjs.com/package/mitt
js
$ npm install --save mitt
- 新建 bus/index.js
js
import mitt from 'mitt'
const $bus = mitt();
export default $bus;
- 子组件分别引入 bus/index.js
- 使用
$bus.on
和$bus.emit
接受和传递数据
parent.vue
js
<template>
<div class="parent">
这是父组件
<div>
<children1></children1>
<children2></children2>
</div>
</div>
</template>
<script setup lang="ts">
import children1 from "./children1.vue"
import children2 from "./children2.vue"
</script>
children1.vue
js
<template>
<div class="children2">
这是子组件2
<p>children1 传过来的数据是:{{ mittData }}</p>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, toRef } from 'vue';
import $bus from '../../bus';
console.log($bus);
let mittData = ref();
onMounted(() => {
$bus.on("car", (data) => {
console.log(data);
mittData.value = data;
})
})
</script>
children2.vue
js
<template>
<div class="children1">
这是子组件1
<button @click="sendMSg">点击按钮给children2传数据</button>
</div>
</template>
<script setup lang="ts">
import $bus from '../../bus';
let num = 0;
const sendMSg = () => {
num ++
$bus.emit("car", `送给 children2 ${num} 辆车`)
}
</script>
4、v-model
v-model的使用:
- 1、v-model 是通过使用子组件时指定事件 @update:modelValue="handler" 和子组件内部的 $emit("update:modelValue")实现的
- 2、有两种写法,v-model="modelValue" 或者 v-model:pageNo=""
- 第一种写法必须要使用 modelValue 作为 prop,,否则无效
- 第二种写法是另外指定 prop 名字
parent.vue
js
<template>
<div class="parent">
这是父组件,inputVal内容是:{{ inputVal }},modelValue内容是:{{ modelValue }}
<br/>
page的值是:{{ pageNo }} - {{ pageSize }}
<!-- <input v-model="inputVal"/> -->
<div>
<children1 :inputVal="inputVal" @update:inputVal="handler"></children1>
<!--
v-model的使用:
1、v-model 是通过使用子组件时指定事件 @update:modelValue="handler" 和子组件内部的 $emit("update:modelValue")实现的
2、有两种写法,v-model="modelValue" 或者 v-model:pageNo=""
- 第一种写法必须要使用 modelValue 作为 prop,,否则无效
- 第二种写法是另外指定 prop 名字
-->
<!-- <children2 v-model:inputVal="inputVal"></children2> -->
<children2 v-model="modelValue"></children2>
<children3 v-model:pageNo="pageNo" v-model:pageSize="pageSize"></children3>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue"
import children1 from "./children1.vue"
import children2 from "./children2.vue"
import children3 from "./children3.vue"
let inputVal = ref("");
const handler = (data) => {
inputVal.value = data;
}
let modelValue = ref("")
let pageNo = ref(1)
let pageSize = ref(10)
</script>
children1.vue
js
<template>
<div class="children1">
这是子组件1
<br/>
这是子组件接收到的值:{{ inputVal }}
<br/>
<button @click="handler">修改inputVal</button>
</div>
</template>
<script setup lang="ts">
let props = defineProps(["inputVal"]);
const $emit = defineEmits(["update:inputVal"])
const handler = () => {
$emit("update:inputVal", props.inputVal + 10)
}
</script>
children2.vue
js
<template>
<div class="children2">
这是子组件2
<br/>
这是子组件接收到的值:{{ modelValue }}
<br/>
<button @click="handler">修改modelValue</button>
</div>
</template>
<script setup lang="ts">
let props = defineProps(["modelValue"]);
const $emit = defineEmits(["update:modelValue"])
const handler = () => {
$emit("update:modelValue", props.modelValue + 10)
}
</script>
children3.vue
js
<template>
<div class="children2">
这是子组件3
<br/>
这是子组件接收到的page值:{{ pageNo }} - {{ pageSize }}
<br/>
<button @click="changePageNo">修改pageNo</button>
<button @click="changePageSize">修改pageSize</button>
</div>
</template>
<script setup lang="ts">
let props = defineProps(["pageNo", "pageSize"]);
const $emit = defineEmits(["update:pageNo","update:pageSize"])
const changePageNo = () => {
$emit("update:pageNo", props.pageNo + 1)
}
const changePageSize = () => {
$emit("update:pageSize", props.pageSize + 10)
}
</script>
5、useAttrs
- 引入 userAttrs
import { useAttrs } from "vue"
- 调用 userAttrs 方法
let attrs = useAttrs();
,以对象方式获取到所有父组件传给子组件的 prop - 直接使用
attrs
注意:
1、此方法跟 vue2 中的 $attr 类似
2、props 与 userAttrs 方法都可以获取父组件传递过来的属性与属性值,但是 props 的优先级更高,如果props接受了,那么 useAttrs 就获取不到了。
parent.vue
js
<template>
<div class="parent">
<div>
<children title="学习useAttr接受父组件的参数" type="primary" size="small"></children>
</div>
</div>
</template>
<script setup lang="ts">
import children from "./children.vue"
</script>
children.vue
js
<template>
<div>
<p>这是子组件</p>
<!-- <p>{{ props }}</p> -->
<p>{{ attrs }}</p>
<p :title="attrs.title" :type="attrs.type" :size="attrs.size">绑定 attrs 的元素 1</p>
<!-- 下面两种方式可以将所有的props属性,一次性全部绑定到 p 标签上
:="attrs" 是 v-bind="attrs" 的简写形式
-->
<p v-bind="attrs">绑定 attrs 的元素 2</p>
<p :="attrs">绑定 attrs 的元素 3</p>
</div>
</template>
<script setup lang="ts">
// const props = defineProps(["title", "type", "size"])
// console.log(props);
import { useAttrs } from "vue"
let attrs = useAttrs();
const props = defineProps(["title"])
console.log(attrs)
console.log(props)
</script>
6、ref 和 $parent
1)ref
- 父组件通过 ref 获取子组件
let child1 = ref();
、(其中,child1 是在子组件标签上定义的 ref 名字) - 子组件将父组件需要用到的变量、方法通过 defineExpose 方法向外暴露
defineExpose({ pNum })
- 父组件通过 child1 使用子组件内暴露到外部的方法或变量
js
// child1.value.cNum1 -= 100;
child1.value.child1Cunt();
demo:
parent.vue
js
<template>
<div class="parent">
<p>这是父组件,要通过ref跟children1交互,让children2通过parent跟父组件交互</p>
<p>pNum的值是:{{ pNum }}</p>
<button @click="handler">跟children1交互</button>
<div>
<children1 ref="child1"></children1>
<children2></children2>
</div>
</div>
</template>
<script setup lang="ts">
import children1 from "./children1.vue"
import children2 from "./children2.vue"
import { ref } from "vue"
let child1 = ref();
console.log(child1);
let pNum = ref(20000)
const handler= () => {
pNum.value += 100;
// child1.value.cNum1 -= 100;
child1.value.child1Cunt();
}
//向外暴露方法,以供子组件通过 $parent 使用
defineExpose({
pNum
})
</script>
child1.vue
js
<template>
<div class="children1">
这是子组件1
<p>children1 的值是:{{ cNum1 }}</p>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue"
let cNum1 = ref(10000)
const child1Cunt = () => {
cNum1.value -= 100
}
// defineExpose 是要对外暴露数据,如果父组件要使用 ref 获取子组件,并对数据进行相关处理,需要把数据暴露出去
defineExpose({
child1Cunt,
cNum1
})
</script>
2)$parent
- 子组件中,绑定方法时,将
$parent
作为参数,$parent
就是父组件 - 父组件需要将相关的变量、方法向外暴露,子组件中才能通过
$parent
去使用
children2.vue
js
<template>
<div class="children2">
这是子组件2
<p>children2 的值是:{{ cNum2 }}</p>
<!-- 这里的 $parent 是父组件,父组件需要将相关的变量、方法通过 defineExpose 向外暴露 -->
<button @click="handler($parent)">children2要跟父组件交互</button>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
let cNum2 = ref(100);
const handler = (parent) => {
cNum2.value += 100;
parent.pNum -= 100;
}
</script>
7、provide和inject
- 用来对祖孙组件进行通信
- provide 和 inject 需要按需引入,
import { inject, provide } from "vue"
- provide 方法接受两个参数,一个是 key,一个是要传入的变量
- inject 方法接受一个参数 key,返回的是 provide 方法中第二个参数(需要响应的数据)
- 使用该方法,孙组件可以修改变量,修改会影响祖组件
parent.vue
js
<template>
<div class="parent">
<p>这是父组件,值是:{{ parentData }}</p>
<div>
<children1></children1>
</div>
</div>
</template>
<script setup lang="ts">
import children1 from "./children1.vue"
import { ref, provide } from "vue"
let parentData = ref(1000)
provide("key", parentData)
</script>
children1.vue
js
<template>
<div class="children1">
<p>这是children1</p>
<children2></children2>
</div>
</template>
<script setup lang="ts">
import children2 from "./children2.vue";
</script>
children2.vue
js
<template>
<div class="children2">
这是children2
<p>拿到的最外层的值是:{{ getData }}</p>
<button @click="handler">修改最外层的值</button>
</div>
</template>
<script setup lang="ts">
import { inject } from "vue";
let getData = inject("key")
const handler = () => {
getData.value += 20
}
</script>
8、pinia
pinia 跟vue2中的vux类似,也是集中式管理状态工具,区别是:
vuex核心概念:state、mutations、actions、getters、modules
pinia核心概念:state、actions(处理异步或修改变量值)、getters
1)选择式API
store/index.ts
js
//引入pinia
import { createPinia } from "pinia";
//createPinia创建到大仓库,大仓库可以自动管理 modules 中的小仓库,组件使用时,直接引用小仓库对应的模块
let store = createPinia();
export default store
store/modules/info.ts
js
import { defineStore } from "pinia";
//定义小仓库,第一个参数是仓库的名字,第二个是配置的对象
//defineStore方法执行后,会返回一个函数,作用是让组件可以获取到这个仓库数据
let info = defineStore("info", {
state: () => { //state 是一个方法,vue2中,state是对象
return {
count: 10,
name: "信息"
}
},
actions: {
changeCount() {
this.count ++
}
}
})
export default info
parent.vue
js
<template>
<div class="parent">
<p>这是父组件,值是:{{ infoData.count }} -- {{ infoData.name }}</p>
<button @click="changeSoreCount">改变store的数据</button>
<div>
<children1></children1>
<children2></children2>
</div>
</div>
</template>
<script setup lang="ts">
import children1 from "./children1.vue"
import children2 from "./children2.vue"
import infoStore from "../../store/modules/info";
let infoData = infoStore();
const changeSoreCount = () => {
/**
* 修改 store 的数据有 3 种方法
* 1、可以直接通过 infoData.count 去修改数据,修改后的数据是响应式的
* 2、也可以通过 infoData.$patch() 方法修改数据,接受一个对象作为参数 {key:value}
* 该方法会将 infoData 中对应的 key 重新赋值,不影响 infoData 中其他数据
* 3、还可以直接调用 inforData 中的方法去修改
*/
// infoData.count ++
infoData.$patch({
count: 12
})
// infoData.changeCount()
}
</script>
children1.vue
js
<template>
<div class="children1">
<p>这是children1</p>
<p>通过store拿到的数据是:{{ infoData.count }}</p>
</div>
</template>
<script setup lang="ts">
import infoStore from "../../store/modules/info"
let infoData = infoStore()
</script>
main.ts
js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import store from "./store";
let app = createApp(App)
app.use(store)
app.mount('#app')
2)组合式API
组合式API内部可以引入 vue 中的方法,如ref、computed等,
store/index.ts
js
//引入pinia
import { createPinia } from "pinia";
//createPinia创建到大仓库,大仓库可以自动管理 modules 中的小仓库,组件使用时,直接引用小仓库对应的模块
let store = createPinia();
export default store
store/modules/details.ts
js
import { defineStore } from "pinia";
import { ref, computed } from "vue";
let details = defineStore("details", () => {
let arr = ref([1,2,3,4])
let total = computed(() => {
return arr.value.reduce((pre:any,next:any) => {
return pre + next
}, 0)
})
return {
arr,
total,
updateArr() {
arr.value.push(5)
}
}
})
export default details
parent.vue
js
<template>
<div class="parent">
<p>这是父组件,值是:{{ detailData.arr }} -- {{ detailData.total }} </p>
<button @click="updateArr">改变store的数据</button>
<div>
<children1></children1>
<children2></children2>
</div>
</div>
</template>
<script setup lang="ts">
import children1 from "./children1.vue"
import children2 from "./children2.vue"
import detailStore from "../../store/modules/details";
let detailData = detailStore()
const updateArr = () => {
// detailData.arr.push(8)
detailData.updateArr()
}
</script>
组合式API 和选择式API区别
- 都要通过 defineStore 方法,创建小仓库,defineStore 接受两个参数,第一个是仓库名,第二个参数在组合式中,要传对象,选择式中,传的是一个函数,函数里面 return 所需要的属性和方法。
9、插槽
插槽分为:默认插槽、具名插槽、作用域插槽
1)默认插槽和具名插槽
具名插槽在使用时可以把v-slot:name
简写为#name
parent.vue
html
<template>
<div class="parent">
<p>这是父组件</p>
<div>
<children1>
<span>往默认插槽里插入一个元素</span>
<!-- v-slot:a 可以简写为 #a -->
<!-- <template #a> -->
<template v-slot:a>
<span>往具名插槽里插入一个元素</span>
</template>
</children1>
</div>
</div>
</template>
<script setup lang="ts">
import children1 from "./children1.vue"
</script>
children1.vue
html
<template>
<div class="children1">
<p>这是children1</p>
<p>这个是默认插槽:<slot>默认插槽的位置</slot></p>
<p>这个是具名插槽:<slot name="a"></slot></p>
</div>
</template>
<script setup lang="ts">
</script>
2)作用域插槽
作用域插槽就是可以传递数据的插槽,子组件可以将数据回传给父组件,父组件可以决定这些回传的数据是以何种结构和外观在子组件内部去展示。
parent.vue
html
<template>
<div class="parent">
<p>这是父组件</p>
<div>
<children1>
<span>往默认插槽里插入一个元素</span>
<!-- v-slot:a 可以简写为 #a -->
<!-- <template #a> -->
<template v-slot:a>
<span>往具名插槽里插入一个元素</span>
</template>
</children1>
<!--作用域插槽:v-slot="{ row }" 从子组件处接受 row -->
<children2>
<template v-slot="{ row }">
<span :style="{color: row.done?'green':'red'}">{{ row.title }}</span>
</template>
</children2>
</div>
</div>
</template>
<script setup lang="ts">
import children1 from "./children1.vue"
import children2 from "./children2.vue"
</script>
children2.vue
html
<template>
<div class="children1">
<p>这是children1</p>
<ul>
<li v-for="item in todos" :key="item.id">
<!-- 将 row 传给父组件 -->
<slot :row="item"></slot>
<!-- {{ item.title }} -->
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
let todos = ref([{id:1,title:"吃饭",done:true},{id:2,title:"睡觉",done:true},{id:1,title:"打豆豆",done:false}])
</script>