十七 组件通信方式
1 props
父传子
//父组件
<script setup>//book来源省略import Subview1 from './Subview1.vue';function updatebook(updatetimes){book.value.updatetimes = updatetimes}
</script>
<template><Subview1 :book="book" :updatebook=updatebook></Subview1>
</template>//子组件
<script setup>import { ref } from 'vue'let updatetimes = ref(0)//修改次数defineProps(['book','updatebook'])
</script>
<template><p>{{ book }}</p><p><button @click="updatebook(updatetimes)">updateprice</button> </p>
</template>
点击updateprice前
点击updateprice后
例子中列表来源于mockjs,点击详细这则设置book为选中的列表中对应位置的值,图里选中的是第一个。
点击详细后,父组件将book传给子组件。
点击updateprice后,子组件将其定义的变量updatetimes传给父组件。
父组件传给子组件,依靠子组件 defineProps设置的变量。
子组件传给父组件,依靠子组件 defineProps设置的方法,该方法体在父组件中设置。
2 自定义事件
用于子向父传数据。
子组件使用defineEmits定义事件,使用emit()触发事件。
父组件对于定义的事件绑定相应方法
//父组件
<script setup>
//list book 来源省略
function updatebook2(book){//修改父组件list中的值list[list_index]= book
}
</script>
<template><Subview1 :book="book" :updatebook="updatebook" @update:book="updatebook2"></Subview1>
</template>//子组件
<script setup>
import { ref } from 'vue'
let updatetimes = ref(0)//修改次数
defineProps(['book','updatebook'])
//定义事件
let emit = defineEmits(['update:book'])
//修改价格
function updateprice(book){book.price = book.price + 1updatetimes.value++book.updatetimes = updatetimesemit('update:book',book)
}
</script>
<template><p><button @click="updateprice(book)">updateprice</button> </p>
</template>
点击修改前
点击两次修改后
子组件定义事件,可随时设置事件调用。
父组件设置子组件事件绑定的对应回调。
子组件事件触发后,会执行父类定义的回调。
官方推荐:
父组件中可以使用 kebab-case 形式来监听。
实际就是例子中update:book变成update-book。
组件中自定义事件也支持修饰符。
组件触发的事件没有冒泡机制,只能监听直接子组件触发的事件,即平级的和跨层的emit监听不到。
3 mitt
可实现任意组件通信。
流程:定义事件并绑定操作->中间件做事件管理->触发事件触发对应操作。
定义:项目名\src\utils\emitter.js
import mitt from "mitt"
const emitter = mitt()export default emitter
父组件
<script setup>
……
import Subview1 from './Subview1.vue';
import Subview2 from './Subview2.vue';
……
function updatebook2(book){list[list_index]= book
}
</script>
<template><div class="main"><h1>View 10</h1><h3>books</h3><div v-for="item in list" :key="item.id" class="book_item"><div class="img"> <img :src="item.cover" alt="item.name"></div><div class="info"> <p>name:{{ item.name }}</p><p>price:{{item.price}}</p></div><div><button @click="showbook(item.id)">详细</button> </div></div><div class="sub" v-show="book"><Subview1 :book="book" :updatebook="updatebook" @update:book="updatebook2"></Subview1> </div><div class="sub"><Subview2></Subview2></div></div>
</template>
Subview1
<script setup>
import { ref } from 'vue'
import mitt from '@/utils/emitter.js';
let updatetimes = ref(0)//修改次数
defineProps(['book','updatebook'])
let emit = defineEmits(['update:book'])
//修改价格
function updateprice(book){book.price = book.price + 1updatetimes.value++book.updatetimes = updatetimesemit('update:book',book)
}
</script>
<template><div ><h2>Subview1</h2><p>{{ book }}</p><p>updatetimes:{{updatetimes}}</p><!-- <p><button @click="updatebook(updatetimes)">updateprice</button> </p> --><p><button @click="updateprice(book)">updateprice</button> <button @click="mitt.emit('buy-book',book)">buybook</button> </p></div>
</template>
<style scoped>
</style>
Subview2
<script setup>
import { reactive } from 'vue'
import mitt from '@/utils/emitter.js';
let buybooks =reactive([])
mitt.on('buy-book',(book)=>{let hasbook =falsefor (var value of buybooks) {if(value.id===book.id){value.num++;hasbook = true;break;}}if(!hasbook){book.num=1;buybooks.push(book)}
})</script>
<template><div ><h2>Subview2</h2><div v-show="buybooks.length>0"><p>buy books</p><div v-for="(item,key) in buybooks" :key="key"><!-- {{key}} -->{{ item.name }} {{ item.num }}x{{item.price}} = {{item.price*item.num}} </div></div></div>
</template>
根据样例,父类展示两个子类,子类中引用mitt定义文件。
Subview1中触发事件,需要defineEmits定义事件名,使用mitte.emit(时间名,参数)调用。
Subview2中接收事件,使用mitt.on(事件名,回调)。
调用mitt.on()后最好再调用mitt.off()进行解绑。
onUnmounted(()=>{mitt.off(事件名)
})
上图为点击一次buybook效果。
上图为点击一次updateprice之后再点击一次buybook效果。
4 v-model
用与变量双向绑定。
<script setup>
……
let book =ref(false)
……
function inputaction($event){book.value.name = $event.target.value
}
function inputaction1(a,$event){book.value.name = a+"_"+$event.target.value
}
</script>
<template><div>book:<input type="text" :value="book.name" @input="inputaction"><br>book: <input type="text" v-model="book.name"><br>book:<input type="text" :value="book.name" @input="inputaction1(1,$event)"></div>
</template>
例子中input两种帮定效果一样。
原始方式就是绑定变量(此时是单向绑定)之后,再绑定方法(此时是双向绑定)用于修改变量。
v-model命令直接可以双向绑定变量。
绑定事件不设置参数时参数默认$event,设置参数时传递$event需要写在参数列表中。
使用在组件中就是实现组件通信,跨组件变量的双向绑定。
若不使用v-model就是在组件中设置props和emits:
//父组件
<Subview :bookname="book.name" @update:bookname="book.name=$event"></Subview>//子组件
defineProps(["bookname"])
defineEmits(["update:bookname"])<input type="text" :value="bookname" @input="$emit('update:bookname',$event.target.value)">
使用v-model后:
//父类
<Subview2 v-model="book.price"></Subview2>//子类
defineProps(["modelValue"])
defineEmits(["update:modelValue"])bookprice:<input type="text" :value="modelValue" @input="$emit('update:modelValue',$event.target.value)">
子组件中modelValue和update:modelValue是固定的。
5.definemodel
vue3.4提供的宏definemodel,可以简化v-model中组件的代码。
父组件
<Subview1 v-model="book.name" v-model:bookprice="book.price"></Subview1>
子组件
let [bookname] = defineModel()
let bookprice = defineModel("bookprice")
function changename($event){bookprice.value =$event.target.value
} <input type="text" :value="bookprice" @input="changename">
父附件可以设置多个v-model,子组件可以解构defineModel或者直接使用。
defineModel生命一个model prop,所以修改方法和prop一样。
6 $attrs
用于当前组件的父组件和子组件传数据。
父组件传给子组件的参数中,若子组件未接收,则存在$attrs。
可以将$attrs传给子组件,子组件再接收。
父类
let test=ref("test")<Subview2 :bookname="book.name" :test="test"></Subview2>
子类
//Subview2defineProps(["bookname"])<Subview3 v-bind="$attrs"></Subview3> //Subview3<script setup>
defineProps(["test"])
</script><template><h3>Subview3</h3><p>test: {{ test }}</p>
</template>
6 $refs
用于获取和修改子组件暴露的数据。
子组件需要使用defineExpose暴露变量。
父组件在子组件上设置ref,用于$refs获取。
父组件
function add_updatetimes(values){console.log(values)console.log(values['sub1'].updatetimes)values['sub1'].updatetimes+=1
}<button @click="add_updatetimes($refs)">add updatetimes</button><Subview1 ref="sub1"></Subview1>
子组件
let updatetimes = ref(0)
defineExpose({updatetimes})<p>updatetimes:{{updatetimes}}</p>
console输出
点击add updatetimes 后
一个父组件可以有多个子组件。
7 $parent
用于子组件获取和修改父组件暴露的数据。
父组件暴露数据后,子组件通过$parent获取和修改。
父组件
let test=ref("test")
defineExpose({test})
子组件
function updatetest(parent){console.log(parent)parent.test = "subview1"
}<button @click="updatetest($parent)">update test</button>
console输出
一个子组件只能由一个父组件,即只能获取最直接的调用其的父附件。
跨组件调用$parent获取不到对应数据。
8 inject&provide
父组件通过provide向之后的子组件传递数据,可跨组件。
子组件通过inject获取父组件传递的数据,可跨组件。
父组件在设置provide,可设置方法,在子组件中可通过改方法向父组件通讯。
父组件
let test1=ref("test1")
function changetest(value){test1.value+="."+value
}
provide("view10_test",{test1,changetest})
子组件
let {test1,changetest} = inject("view10_test","默认值")<p>test1: {{ test1 }}
</p><button @click="changetest('new test')" >change test</button>
点击change test后