哈喽,各位前端小萌新,我是你们的“代码导游”!今天我们要聊聊Vue3中两个超级重要,又常常让人挠头的小伙伴:响应式和父子组件通信。别怕,我会用最“接地气”的方式,带你轻松搞定它们!
响应式:让你的数据“活”起来!
ref vs. reactive:一场“简单”与“复杂”的较量
关于响应式的底层我之前也写过一篇文章,感兴趣的话可以看看,贴近底层一点JavaScript 的 响应式 与 Proxy 代理
首先,我们来认识一下Vue3的响应式“双雄”:ref和reactive。这俩哥们儿都是让你的数据“动起来”的神器,但用法上却有点小区别。
ref:简单数据类型的“小管家”
想象一下,你家有个小本本,上面记录着一些简单的数字或文字,比如你的年龄、你今天的卡路里摄入量,这就是 ref 的用武之地。它专门负责管理像数字、字符串、布尔值这种“简单”的数据类型。
import { ref } from 'vue'
const counter = ref(0); // 初始值是0
// 更新计数器:
counter.value++; // 注意要通过.value访问哦!
小Tips: ref 为什么需要 .value?其实它背后偷偷用了 Object.defineProperty 来实现响应式,这个方法性能高,能精准地“监视”数据的变化。 就像我们小时候用的那种小本本,一有改动,就得一笔一画的记下来。
reactive:复杂数据类型的“大管家”
如果你的数据不再是简单的“小本本”,而是一个复杂的“家庭档案”,里面有姓名、年龄、爱好等等,那就要请出 reactive 了。它专门用来管理像对象、数组这种“复杂”的数据类型。
import { reactive } from 'vue'
const state = reactive({
name: 'Alice',
age: 25
});
// 更新状态:
state.name = 'Bob';
state.age++;
小Tips: reactive 背后用了更高级的 Proxy 代理,可以监听对象所有属性的变化。但Proxy开销比较大,所以说“简单类型”数据用ref,“复杂类型”数据用reactive是最佳选择。 就像家庭档案,内容繁多,需要一个更专业的“管家”来管理。
特性 | ref | reactive |
---|---|---|
用途 | 处理简单数据类型(数字、字符串、布尔值等) | 处理复杂数据类型(对象、数组等) |
访问方式 | .value | 直接访问属性 |
原理 | Object.defineProperty | Proxy |
性能 | 较好 | 相对开销大 |
父子组件通信:跨越组件的“爱”
接下来,我们聊聊组件之间如何“眉来眼去”——也就是父子组件之间的通信。
props:爸爸给儿子的“零花钱”
父组件可以通过 props 向子组件传递数据,就像爸爸给儿子零花钱一样,这是单向的数据流,子组件不能直接修改 props 的值,必须“请求”父组件的“同意”才能修改数据。
父组件(Parent.vue):
<template>
<div>
<h2>父组件</h2>
<ChildComponent :title="title"/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './components/childComponent.vue'
const title = ref("来自父组件的消息")
</script>
子组件 (ChildComponent.vue):
<template>
<div>
<h2>{{ title }}</h2>
</div>
</template>
<script setup>
import { defineProps } from 'vue';
defineProps({
title:{
type:String, // 指定类型
required:true // 必填项
}
})
</script>
小Tips: 子组件必须用 defineProps 来声明它要接收哪些 props,就像你跟爸爸要零花钱,得先说清楚你要多少、干什么用。 而且props 里面的属性可以定义类型、是否是必填项,这样可以提高代码的可读性和健壮性!
emit + 自定义事件:儿子给爸爸的“小报告”
光有“零花钱”可不行,儿子还得时不时向爸爸汇报情况。这时就要用到 emit 和自定义事件了。子组件用 emit 发送消息,父组件用 @ 监听并处理,就像儿子发微信给爸爸汇报情况。
子组件 (ChildComponent.vue):
<template>
<div>
<h2>{{ title }}</h2>
<button @click="sentMessageToParent">发送信息给父组件</button>
</div>
</template>
<script setup>
import {
defineProps,
defineEmits,
ref
} from 'vue';
const chileMessage=ref('Hello Parent!')
defineProps({
title:{
type:String,
required:true
}
})
const emit = defineEmits(['child-message'])
const sentMessageToParent = () =>{
emit('child-message',chileMessage.value)
}
</script>
父组件 (Parent.vue):
<template>
<div>
<h2>父组件</h2>
<p>来自子组件的消息:{{ messageFromChild }}</p>
<ChildComponent @child-message="handleChildMessage" :title="title"/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './components/childComponent.vue'
const title = ref("来自父组件的消息")
const messageFromChild = ref("")
const handleChildMessage = (message) =>{
console.log('子组件发送消息',message);
messageFromChild.value = message;
}
</script>
小Tips: defineEmits(['child-message']) 就像子组件向父组件“备案”,声明自己会发出一个叫 child-message 的事件。然后,用 emit('child-message', message) 发出消息,并在父组件中用 @child-message="handleChildMessage" 监听这个事件。
handleChildMessage就是父组件接收到消息的处理函数。这里要特别注意,事件名称要保持一致,这样父子组件才能成功通信!
子组件暴露属性和方法
有时候,父组件不仅想接收子组件的消息,还想直接调用子组件的属性或方法。 这时,我们就可以用到 defineExpose 了。
子组件(Child.vue):
<template>
<div>
Child
</div>
</template>
<script setup>
import { defineExpose } from 'vue'
defineExpose({
childName:'这是子组件的属性',
someMetthod(){
console.log("这是子组件的方法");
}
})
</script>
父组件(Parent.vue):
<template>
<div>
<Child ref="comp"/>
<button @click="handlerClick">按钮</button>
</div>
</template>
<script setup>
import Child from './child.vue'
import {
ref
} from 'vue'
const comp = ref(null) // 标记一个DOM元素 null 组件还没有挂载,DOM也不在
const title=ref('hello') // 标记一个普通变量
const handlerClick = () =>{
console.log(comp.value);
console.log(title.value);
comp.value.someMetthod()
}
</script>
小Tips: 在父组件中,我们需要使用 ref 来获取子组件的实例(这也是ref一个功能),才能访问子组件暴露出来的属性和方法。 使用 ref 获取子组件实例,需要给子组件添加 ref 属性。
在这里我们可以清楚看到,当ref绑定的是一个数组对象时它的value值是一个Proxy代理对象,我们可以直接通过这个代理对象来操作和使用子组件暴露出来的元素和方法。
作者:answerball
链接:https://juejin.cn