写在前面
记录一下最近在学习vue3组件间通信以及使用时需要注意的地方,小萌新一枚,不对的地方欢迎批评指正~🤦♂️
七种常用组件间通信方式
以下代码都基于vue3.2+
1.Props
vue最最最常见的通信方式之一,父组件传值给子组件,通过v-bind在子组件上绑定属性,子组件中使用defineProps来接收父组件传的值,话不多说,直接上代码
// 父组件
<template>
<div>{{info}}</div>
<Child :info="info" :msg="msg"></Child>
</template>
<script setup>
const msg = ref('山外青山楼外楼')
const info = reactive({ hobbit1: '唱', hobbit2: '跳', hobbit3: '打篮球' })
watch(info, (newVal, oldVal) => {
console.log('info 发生变化', newVal)
})
</script>
// Child子组件
<template>
<button @click="test">修改props测试</button>
</template>
<script setup>
// defineProps无需引用
const props = defineProps({
msg: { type: String, default: '你干嘛~', required: true },
info: { type: Object, default: () => {}, required: true }
})
console.log('props', props.msg, props.info);
// props 山外青山楼外楼 {hobbit1: '唱', hobbit2: '跳', hobbit3: '打篮球'}
// 疑问:defineProprs引入的变量还具有响应式吗? 是否能进行修改呢?
// 下面尝试一下:
props.msg = '别说话'
// 结果发现:报警告,不能修改只读的属性
// [Vue warn] Set operation on key "msg" failed: target is readonly.
// 结论:使用defineProprs引入的变量是readOnly格式,不能进行修改
// 既然这样不能修改,那能不能使用toRef来或者toRefs函数来把props中的对象变成响应式变量呢?
// 这样不就能实现修改props来反向修改父组件吗?
// 我也是这样想的,于是测试后发现
let newInfo = toRef(props.info, 'hobbit3')
const test = () => {
newInfo.value = '不打了'
}
// 点击子组件按钮运行后发现父组件中的info发生了变化! watch函数也响应了!!
// console:info 发生变化 Reactive<Object>
// toRef的作用和toRefs的作用区别只在于单个属性是响应式和整个对象都是响应式的区别
// 所以不用再测试我们也能知道toRefs也能把props变成响应式进行修改
// 但是!
let newInfo = props.info // 如果我这样直接接收
// 上面这种写法就相当于 let newInfo = toRefs(props.info)
console.log(newInfo) // 会发现newInfo是个响应式对象
newInfo.hobbit1 = '不唱了' // 也能直接修改父组件的info... 然后触发父组件的watch
// 上面这也可以? 那我解构props呢?
let { info, msg } = props
// 测试后发现info也能有响应式!!! 那我继续解构info试试
let { hobbit1, hobbit2, hobbit3 } = info
// 到这里后hobbit1, hobbit2, hobbit3这几个属性就失去了响应式,变成普通的字符串了
// 结论:当使用toRef或者toRefs接收props中的某个对象类型的参数,
// 或者使用解构语法解构出props中的某个对象类型的参数,
// 或者直定义一个变量接收“对象类型参数”那么这个参数被修改的话
// 父组件中的响应元素也会被修改,视图上也具也会有响应式的修改。
// 我们知道toRef和toRefs都是基于响应式对象才能使用的,也就是使用reactive创建的变量
// 但是上面我们都没对简单类型的变量进行修改测试,也就是使用ref创建的简单变量
// 如果我们要修改由ref创建的变量该怎么做呢?能不能做? 不急 我们接着往下
</script>
2.Emit
也是最常见用的子组件对父组件通信的方法之一,子组件通过使用defineEmits来注册事件,父组件在子组件上绑定过相应的函数来接收。
// 父组件
<template>
<Child @emitFun="exceptEmit" v-model:msg="msg"></Child>
</template>
<script setup>
const msg = ref('山外青山楼外楼')
const exceptEmit = (data) => {
console.log('这是接收到来自子组件的信息: ', data);
}
// 点击子组件后输出: 这是接收到来自子组件的信息: 你干嘛 ~ 哎哟
// 子组件触发'update:msg'函数之后,父组件的msg就被修改了
</script>
// Child子组件
<template>
<button @click="getVoice">触发叫声</button>
</template>
<script setup>
// defineEmits无需引用
const emit = defineEmits(['emitFun', 'update:msg'])
const getVoice = () => {
emit('emitFun', '你干嘛 ~ 哎哟')
emit('update:msg', '唱跳rap打篮球')
}
// 上面我们测试了通过toRef等方式来修改porps中的复杂类型变量,发现能实现响应式
// 而简单类型的props没法通过上边的方法修改, 如果要修改的话有什么办法吗?
// 有的, 通过emit事件来修改简单类型的变量 也可以多个,只要声明就好了
// emit('update:msg', '唱跳rap打篮球')
</script>
3.Provide/Inject
也是最常见用的子组件对父组件通信的方法之一,子组件通过使用defineEmits来注册事件,父组件在子组件上绑定过相应的函数来接收。
// 父组件
<template>
<Child></Child>
</template>
<script setup>
let provideValue = reactive({name: '两年半'})
let provideValue2 = ref('个人练习生')
provide('provideValue', provideValue)
provide('provideValue2', provideValue2)
</script>
// Child子组件
<template>
<Child></Child>
</template>
<script setup>
let a = inject('provideValue')
let b = inject('provideValue2') // 可以直接改原数据
</script>
4.DefineExpose
子组件通过使用defineExpose暴露这个组件中的一些方法和属性,让父组件能够调用到,父组件在子组件山声明ref,通过模板引用的方式获取到当前组件的实例。
// 父组件
<template>
<Child ref="comp"></Child>
</template>
<script setup>
const comp = ref(null) // comp就为子组件所暴露给父组件的属性和方法,会自动解包
const changeProps = () => {
console.log(comp.value) // 输出c1、c2和changeExposeVlue方法
comp.value.c2 = 100 // 修改成功,并且拥有响应式
comp.value.changeExposeVlue() // '调用子组件的方法'
}
</script>
// Child子组件
<template></template>
<script setup>
let c1 = reactive({a: 1, b: 2})
let c2 = ref(0)
let changeExposeVlue = () => {
console.log('调用子组件的方法')
// c2.value = 1 // 能够正常修改
}
// 暴露这个组件中的一些方法和属性,让父组件能够调用到 同时也能修改
defineExpose({
c1,
c2,
changeExposeVlue
})
</script>
5.UseAttrs
在父组件中给子组件v-bind绑定属性或是直接给定属性,子组件中通过使用useAttrs来接收父组件传递的参数,如果使用了props接收了部分参数,那么useAttrs就会接收剩下的那部分参数。使用useAttrs情况应该是相对来说较为罕见的,因为可以在模板中直接通过 $slots 来访问它们
// 父组件
<template>
<Child :name="name" :hobbit="hobbit" sports="rap"></Child>
</template>
<script setup>
const name = ref("鸽鸽")
const hobbit = reactive({h1: '唱歌', h2: '打篮球'})
</script>
// Child子组件
<template></template>
<script setup>
const props = defineProps({
name: String
})
const attrs = useAttrs()
console.log(attrs) // { hobbit:{h1: '唱歌', h2: '打篮球'}, sports: "rap" }
// 测试发现用useAttrs()返回属性 经过toRef转换也能保持和父组件属性响应式
// 其他的几种我就不写了哦 大家可以自己测试一下是否有响应式
</script>
6.V-model
这个通信方法也可以归于emit,同样也是使用defineEmits来声明函数方法,在父组件中不需要使用函数来接收,写法上会有一些不同,上代码~
// 父组件
<template>
<Child v-model:name="name" v-model:hobbit="hobbit" ></Child>
</template>
<script setup>
const name = ref('鸽鸽')
const hobbit = reactive({h1: '唱歌', h2: '打篮球'})
</script>
// Child子组件
<template>
<button @click="emitFather">emit</button>
</template>
<script setup>
const emit = defineEmits(['update:name', 'update:hobbit'])
const emitFather = () => {
emit('update:name', '哥哥')
emit('update:hobbit', {h: '这只是我的爱好'})
}
</script>
7.VUEX
在vue3版本中,组件之间的数据和方法通信感觉比2版本的要更加丰富,toRef、reactive、等响应式API的使用能够降低对vuex的使用,proxy的双向绑定,让我们在顶层父组件中定义数据自上而下的调用在小项目中感觉会更有作用,vuex的用法在vue3中和vue2一样,并没有多少改变,大家可以自行看vuex文档了解, 还有一个新的状态管理工具Pinia大家感兴趣的话也可以去学习一下,感觉和vuex差不多,也容易上手,其它的我就不多赘述啦。
最后
如果本篇文章对你有帮助的话,求赞求转发~,也欢迎各位同学在评论区讨论指正~