目录
-
首先,我们要明白,数据流是自顶向下的,也就是说,只能上层的修改下层的,不能下层的修改上层的(正确的做法,应该是下层的通知上层的,让上层的去修改)
自定义属性props(父向子)
- 可以用于父向子传值
-
vue3中,子组件用
defineProps
这个hook来接收父组件传来的数据。可以是一个数组,也可以是一个对象 -
defineProps
不需要导入,defineProps
接收到的数据,可以直接进行使用
父组件
<template>
<div class="father">
<h1>father</h1>
<Son :count="count"></Son>
</div>
</template>
<script setup>
import { ref } from 'vue'
import Son from './Son.vue'
const count = ref(100)
</script>
<style lang="scss" scoped>
.father {
height: 300px;
background-color: pink;
padding: 20px;
}
</style>
子组件
<template>
<div class="son">
<h3>son</h3>
<p>父组件传来的数据: {
{ count }}</p>
</div>
</template>
<script setup>
// 1. 数组
// const props = defineProps(['count'])
// 2. 对象,可以对父组件传来的数据进行一下校验
const props = defineProps({
count: {
type: Number,
default: 0,
},
})
</script>
<style lang="scss" scoped>
.son {
height: 100px;
background-color: #91beff;
}
</style>
自定义事件(子向父)
可以用于子向父传值
-
子组件通过
emit
向父组件传递一个自定义事件 -
父组件,在子组件标签上接收自定义事件,并声明处理函数
子组件
<template>
<div class="son">
<h3>son</h3>
<button @click="sendData">点击向父组件传值</button>
</div>
</template>
<script setup>
// 子组件通过 defineEmits 这个hook声明自定义事件
const $emit = defineEmits(['sendData'])
const sendData = () => {
// 1. 子组件通过 emit 向父组件传递一个自定义事件(第一个参数是自定义事件名,第二个参数是传递的参数)
$emit('sendData', 999)
}
</script>
<style lang="scss" scoped>
.son {
height: 100px;
background-color: #91beff;
}
</style>
父组件
<template>
<div class="father">
<h1>father --- 接收到的子组件传过来的值:{
{ count }}</h1>
<!-- 2. 父组件,在子组件标签上接收自定义事件,并声明处理函数 -->
<Son @sendData="sendData"></Son>
</div>
</template>
<script setup>
import { ref } from 'vue'
import Son from './Son.vue'
let count = ref(0)
// 3. 处理函数,如果子组件传递了参数,就可以写形参进行接收
const sendData = (val) => {
console.log(val) // 接收到的子组件的参数
// 可以把参数转存到声明的变量中,这样就可以直接使用了
count.value = val
}
</script>
<style lang="scss" scoped>
.father {
height: 300px;
background-color: pink;
padding: 20px;
}
</style>
补充:vue2和3的自定义事件
-
在
vue2
中,如果在组件标签上写click
,mouseEnter
,focus
等等这些原生的事件,它会以为就是自定义事件,不会以为是原生事件。如果想要它以原生的事件执行,则需要添加.native
修饰符
<组件名 @click.native = 'handleClick'></组件名>
-
而
vue3
,如果绑定原生事件,它就会以为就是原生事件。只要在子组件内,条件满足就会触发
<组件名 @click = 'handleClick'></组件名>
ref获取子组件实例
-
ref 可以获取真实的
DOM
节点,也可以获取组件实例。获取子组件实例后,就可以通过子组件实例,直接修改和使用子组件定义的变量,也可以直接调用子组件的方法 -
组件内部的数据是封闭保护的,如果向让外界使用,可以使用
defineExpose
这个hook
进行暴露
父组件
<template>
<main class="father">
<h1>father</h1>
<Son ref="son"></Son>
</main>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import Son from './Son.vue'
// 子组件的实例化
let son = ref()
// 只有挂载到父组件之后,才能使用子组件的属性和方法
onMounted(() => {
console.log(son.value.num)
})
</script>
<style>
.father {
height: 400px;
background-color: pink;
padding: 20px;
}
</style>
子组件
<template>
<div class="son">
<h2>son</h2>
</div>
</template>
<script setup>
import { ref } from 'vue'
let num = ref(10)
// 需要使用 defineExpose 进行对外暴露(方法和属性都可以)
defineExpose({
num,
})
</script>
<style>
.son {
height: 200px;
background-color: skyblue;
}
</style>
$parent获取父组件实例
-
可以在子组件中通过
$parentz
这个参数获取父组件的实例 -
组件内部的数据是封闭保护的,如果向让外界使用,可以使用
defineExpose
这个hook
进行暴露
子组件
<template>
<div class="son">
<h2>son</h2>
<button @click="getParentData($parent)">获取父组件的数据</button>
</div>
</template>
<script setup>
const getParentData = (parent) => {
console.log('获取到了父组件实例', parent)
}
</script>
<style>
.son {
height: 200px;
background-color: skyblue;
}
</style>
父组件
<template>
<main class="father">
<h1>father</h1>
<Son></Son>
</main>
</template>
<script setup>
import { ref } from 'vue'
import Son from './Son.vue'
let count = ref(100)
// 需要使用 defineExpose 进行对外暴露(方法和属性都可以)
defineExpose({
count,
})
</script>
<style>
.father {
height: 400px;
background-color: pink;
padding: 20px;
}
</style>
作用域插槽向父组件传值
-
子组件可以利用作用域插槽向父组件传值
-
作用域插槽就是可以传递数据的插槽,子组件可以将数据回传给父组件,父组件可以决定这些回传的数据,是以何种结构或者外观在子组件内部去展示
子组件
<template>
<div class="son">
<h2>son</h2>
<p>--------默认插槽---------</p>
<slot></slot>
<p>--------具名插槽。name指定插槽的名字---------</p>
<slot name="superman"></slot>
<p>--------作用域插槽。通过属性绑定,向父组件提供数据---------</p>
<slot name="dc" :list="list"></slot>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const list = ref([
{
name: '张三',
age: 21,
color: 'pink'
},
{
name: '里斯',
age: 22,
color: 'skyblue'
},
{
name: '王五',
age: 23,
color: 'hotpink'
},
])
</script>
<style>
.son {
height: 150px;
background-color: skyblue;
}
</style>
父组件
<template>
<main class="father">
<h1>father</h1>
<Son>
<p>默认插槽里面的内容</p>
<template #superman>具名插槽中的内容</template>
<template #dc="{ list }">
<ul>
<li v-for="i in list" :key="i.name" :style="{ backgroundColor: i.color }">
{
{ i.name }} -- {
{ i.age }}
</li>
</ul>
</template>
</Son>
</main>
</template>
<script setup lang="ts">
import Son from './Son.vue'
</script>
<style>
.father {
height: 300px;
background-color: pink;
padding: 20px;
}
</style>
兄弟组件传值
可以利用第三方插件 mitt 实现
首先进行安装
yarn add mitt
使用:
-
在
src
目录下创建一个bus
的文件夹,里面创建一个mitt.js
的文件。(文件名随便起)
import mitt from 'mitt'
const emitter = mitt()
export default emitter
-
发送方:导入
mitt.js
并用emit
发送数据
<template>
<div class="son">
<h3>son</h3>
<button @click="givingGifts">点我,给妹妹送一件礼物</button>
</div>
</template>
<script setup>
import emitter from '@/bus/mitt'
const givingGifts = () => {
// 发送方 emitter.emit('事件名', 参数)
emitter.emit('foo', { car: 'QQ车' })
}
</script>
<style lang="scss" scoped>
.son {
height: 100px;
background-color: #91beff;
}
</style>
-
接收方:导入
mitt.js
并用on
接收兄弟组件发来的数据
<template>
<div class="sister">
<h2>sister</h2>
<p>接收到了哥哥的礼物:{
{ gifts }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
import emitter from '@/bus/mitt'
let gifts = ref()
// 接收方 emitter.on('事件名', (参数) => { ... })
emitter.on('foo', (val) => {
gifts.value = val.car
})
</script>
<style lang="scss" scoped>
.sister {
height: 100px;
background-color: #91beff;
}
</style>
父子双向数据绑定
使用 v- model 进行父子组件之间的双向数据绑定
-
v-model
不光用在表单上,也可以用在组件上。用在组件上绑定值后,子组件只需要defineProps
接收即可 -
v-model
用在组件上,默认就是v-model:modelValue
,也就是默认绑定modelValue
-
组件上可以通过v-model传递多个值
父组件
<template>
<main class="father">
<h1>father</h1>
<Son v-model="count" v-model:str="str"></Son>
</main>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Son from './son.vue'
let count = ref(100)
let str = ref('str')
</script>
<style>
.father {
height: 300px;
background-color: pink;
padding: 20px;
}
</style>
子组件
<template>
<div class="son">
<h2>son</h2>
<p>接收到的父组件传来的值:{
{ modelValue }} -- {
{ str }}</p>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
modelValue: {
type: Number
},
str: String
})
</script>
<style>
.son {
height: 100px;
background-color: skyblue;
}
</style>
这只是单纯的父向子传递了数据,如果子组件想要修改,则还需要向父组件发送自定义事件,通知父组件进行修改
实现父子通信
父组件
<template>
<main class="father">
<h1>father</h1>
<Son v-model="count" v-model:str="str" @changeCount="changeCount"></Son>
</main>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Son from './son.vue'
let count = ref(100)
let str = ref('str')
const changeCount = (val: number) => {
count.value = count.value + val
}
</script>
<style>
.father {
height: 400px;
background-color: pink;
padding: 20px;
}
</style>
子组件
<template>
<div class="son">
<h2>son</h2>
<p>接收到的父组件传来的值:{
{ modelValue }} -- {
{ str }}</p>
<button @click="changeCount">修改父组件传来的数值</button>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
modelValue: {
type: Number
},
str: String
})
const $emits = defineEmits(['changeCount'])
const changeCount = () => {
$emits('changeCount',1)
}
</script>
<style>
.son {
height: 200px;
background-color: skyblue;
}
</style>
useAttrs获取父组件传来的属性和事件
-
可以获取到父组件传递过来的属性和事件
-
没有被
definedProps
声明接收的,都会被useAttrs
兜底接收
父组件
<template>
<main class="father">
<h1>father</h1>
<Son type="primary" size="small"></Son>
</main>
</template>
<script setup>
import Son from './Son.vue'
</script>
<style>
.father {
height: 400px;
background-color: pink;
padding: 20px;
}
</style>
子组件
<template>
<div class="son">
<h2>son</h2>
</div>
</template>
<script setup>
import { useAttrs } from 'vue'
let $attrs = useAttrs()
console.log($attrs)
</script>
<style>
.son {
height: 200px;
background-color: skyblue;
}
</style>
封装第三方的组件时
-
可以用
v-bind
绑定$attrs
对象。多用于对第三方ui库的组件进行二次开发
<el-button :='$attrs'>
<slot></slot>
</el-button>
也可以接收事件
<Son type="primary" size="small" @click="clickSon"></Son>
深层组件传值
深层组件,就是有很多层的嵌套关系。类似于祖先和子孙的关系
使用 provide 和 inject 进行深层组件的传值
-
祖先组件通过
provide
这个hook
向子孙组件提供数据。它下面的所有子孙组件都可以使用提供的数据 -
子孙组件通过
inject
这个hook
接收祖先提供的数据
祖先组件
<template>
<main class="father">
<h1>father</h1>
<Son></Son>
</main>
</template>
<script setup lang="ts">
import { ref,provide } from 'vue'
import Son from './son.vue'
let count = ref(100)
// 提供者:向子孙组件提供数据
provide('count',count)
</script>
<style>
.father {
height: 300px;
background-color: pink;
padding: 20px;
}
</style>
中间组件
<template>
<div class="son">
<h2>son</h2>
<GrandSon></GrandSon>
</div>
</template>
<script setup lang="ts">
import GrandSon from './GrandSon.vue';
</script>
<style>
.son {
height: 100px;
background-color: skyblue;
}
</style>
子孙组件
<template>
<div class="son">
<h3>grandSon</h3>
<p>从祖先组件接收过来的数据:{
{ count }}</p>
</div>
</template>
<script setup lang="ts">
import { inject } from 'vue'
let count = inject('count')
</script>
<style>
.son {
height: 100px;
background-color: skyblue;
}
</style>
Pinia实现任意组件之间通信
-
核心:
state
,actions
,getters
选项式用法
定义:
import { defineStore } from 'pinia'
let userInfoStore = defineStore('user', {
state: () => {
return {
name: '张三',
}
},
getters: {},
actions: {
changeName(name: String) {
this.name = name
},
},
})
// 对外暴露
export default userInfoStore
使用:
<template>
<main>
<p>从数据仓库获取到的数据:{
{ userInfo.name }}</p>
<button @click="changeName">修改数据仓库提供的name的值</button>
</main>
</template>
<script setup lang="ts">
import userInfoStore from '@/stores/counter'
const userInfo = userInfoStore()
const changeName = () => {
// 1. 直接进行修改
// userInfo.name = '李四'
// 2. 通过 $patch 方法进行调用
// userInfo.$patch({
// name: '李四',
// })
// 3. 通过仓库调用自身的方法去修改仓库的数据
userInfo.changeName('李四')
}
</script>
组合式用法
定义:
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
export default useCounterStore
使用:
<template>
<main>
<h1>首页</h1>
<p>
数据仓库提供的数据:{
{ useCounter.count }} -- {
{ useCounter.doubleCount }}
</p>
<button @click="changeCount">使用数据仓库提供的方法改变count值</button>
</main>
</template>
<script setup lang="ts">
import useCounterStore from '@/stores/counter'
const useCounter = useCounterStore()
const changeCount = () => {
useCounter.increment()
}
</script>