父子组件通信
props
prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。
任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域。
- 除了传递字符串外,传递其他类型必须
:动态绑定 - 可传递一个函数,在子组件中执行父组件中的函数,并传递参数
- 给组件传递非 props 属性(组件内没有接收props)默认绑定到组件的根元素上
// 写法一:数组形式
props:['movies']
// 写法二:类型限制
props:{movies:Array}
// 写法三:提供默认值
props:{
name:{type:String, required:true, default:'张三'},
age:{type:Number, required:true, default:20}
}
注意事项:
- 对象或数组的
default必须从一个函数获取,避免组件间引用数据类型共享。default(){ return []} - 不要在子组件直接修改父组件传递来的 props 数据。应该由子组件 $emit 一个事件,让父组件监听到事件并修改数据。或者子组件接收到 props 赋值给子组件的一个 data 数据变量,使用该数据变量去做修改。
- prop 会在组件实例创建之前进行验证,所以实例的 property,如:data、computed等,在
default或 validator 函数中是不可用的。
缺陷
如果需要向非子后代传递数据必须多层、逐层传递;兄弟组件间也不能直接 props 通信,必须借助父组件。
自定义事件
子组件向父组件传递数据
- 子组件中绑定事件监听
// 触发事件
<button @click='btnClick'>删除父组件中嚣张的p</button>
// 执行事件处理函数向父组件派发事件
methods:{
btnClick() {
this.$emit('tellParent', {name:'lxx'})
}
}
- 父组件中监听子组件派发的自定义事件
<Home @tellParent='deleteP'>
<p ref='refP'>谁能把我删除!</p>
methods:{
deleteP(value) {
this.$refs.refP.remove()
console.log(value) // { name:'lxx' } 获取子组件传来的值
}
}
Vue3 中的变化
emits 选项声明组件可以派发的事件
export default {
emits:['deleteP'],
setup(props, {emit}) {
const value = { name:'lxx' }
emit('deleteP', value)
}
}
emits 对象方式,目的是进行参数的验证。验证不通过,参数仍然会被传递,但控制台会报警告:Invalid event arguments: event validation failed for event "addN".
emits:{
add:null, // 不验证
sub:null,
addN:(n)=> {
if(n > 5) {
return true
}
return false
}
}
缺陷
只适用于子组件向父组件发送数据,不适合隔代组件或兄弟组件间通信
非父子组件通信
Provide 和 Inject
用于非父子组件之间共享数据:深度嵌套的组件,子孙组件要获取父组件的部分内容。父组件不需要知道哪些子孙组件使用了它 provide 的 property,子孙组件也不需要知道 inject 的 property 来自哪里。
普通数据
// App.vue
provide:{
sayHi:'hi,lxx!'
}
// 子孙组件
<h2>使用祖父或父组件提供的数据:{{sayHi}}</h2>
export default {
name:'children',
inject:['sayHi']
}
访问组件实例属性
如果尝试在 provide 中访问组件的实例属性则会报错。因为 provide 对象中的 this 指向 script 作用域中的 this,结果是 undefined(单文件组件开发方式,ES Module是严格模式,严格模式下的全局 this 是 undefined)
// App.vue
provide:{
colorsLength:this.colors.length // Error Cannot read property 'colors' of undefined
},
data() {
return {
colors:['red', 'blue', 'green']
}
}
要访问组件实例属性,我们需要将 provide 转换为返回对象的函数。因为函数是有作用域的,这里函数的作用域中的 this 指向组件实例。
// App.vue
<button @click=handleAdd>增加一个name</button>
provide() {
return {
colorsLength:this.colors.length // ok this指向 vue组件实例
}
},
data() {
return {
colors:['red', 'blue', 'green']
}
},
methods:{
handleAdd() {
this.names.push('ddd')
}
}
以上代码给 colors 数组 push 新的成员,不会让传递给子孙组件的 colorsLength 响应。
保持响应性
上面代码,如果更改了 colors ,这个变化不会反应在 inject 的 colorsLength 属性。这是因为默认情况下,provide/inject 绑定并不是响应式的。
使用组合式API computed 函数,来实现响应性。当 data 中的 colors 属性被改变,子孙组件 inject 的数据就会响应。
// App.vue
provide() {
return {
colorsLength:computed(()=> this.colors.length)
}
},
data() {
return {
colors:['red', 'blue', 'green', 'purple'] // 新push一个purple
}
}
// 子孙组件
<template>
<h2>colors数量:{{colorsLength.value}}</h2>
</template>
<script>
export default {
inject:['colorsLength'] // colorsLength会变为4
}
</script>
setup 书写方式
// App.vue
import {computed, provide, ref} from 'vue'
const colors = ref(['red', 'green', 'blue'])
provide('message', computed(()=> colors.value.length))
// 子孙组件
<p>Message: {{ message }}</p>
<script setup>
import {inject} from 'vue'
const message = inject('message')
</script>
全局事件总线库-mitt
-
封装一下mitt
// utils/emitter.js import mitt from 'mitt' const emitter = mitt() // mitt函数返回一个对象 export default emitter -
派发一个事件
import emitter from '@/utils/emitter' // 通过点击事件派发一个全局事件 methods:{ publishEvent() { emitter.emit('aaa', 'hello') } } -
监听派发的事件,在 created 或 mounted 生命周期函数中。
// 兄弟组件或子组件 import emitter from '@/utils/emitter' export default { created() { emitter.on('aaa', (payload)=> { console.log(payload) }) } } -
取消事件监听。在 Vue2.x
beforeDestory()或 Vue3.xbeforeUnmount()生命周期钩子函数中取消。// 取消全部监听事件 emitter.all.clear() // 使用处理程序引用取消 function onFoo() {} emitter.on('foo', onFoo) // listen emitter.off('foo', onFoo) // unlisten
全局状态管理
Vuex
Vuex就是一个前端的公共仓库(状态管理工具),用于平行组件间的通信(传值,双向数据绑定)
请阅读 Vuex 专栏文章
Pinia
拥有组合式 API的 Vue 状态管理库,主要面向 Vue3.x,使用更加友好。
请阅读 Pinia 专栏文章