Code前端首页关于Code前端联系我们

Vue3里子组件怎么把数据传给父组件?

terry 1个月前 (09-10) 阅读数 52 #Vue
文章标签 Vue3 组件通信

做前端项目时,组件通信是绕不开的事儿,尤其是Vue3项目里,子组件想把自己的数据传给父组件,新手往往摸不着门道,今天就从原理、步骤到实战例子,把“Vue3子传父”这件事掰碎了讲明白~

子传父的核心逻辑是啥?

Vue里组件关系是分层的,父组件像“家长”,子组件是“孩子”,子传父不像父传子能直接props往下丢,得靠“自定义事件”来沟通,简单说就是:子组件主动“喊一嗓子”(触发事件),把数据带出去;父组件“听着这嗓子”(绑定事件),接住数据后处理。

在Vue3的<script setup>语法糖里,得用defineEmits来声明要触发的事件,相当于告诉Vue:“我这孩子要对外喊哪些事儿”,这样做既规范,还能配合TypeScript做类型检查,避免事件名打错这类低级错误。

具体实现分哪几步?

想让子传父跑通,得按“声明事件→触发事件→父组件监听”这三步来:

步骤1:子组件声明可触发的事件

在子组件的<script setup>里,用defineEmits来定义事件,比如子组件想传用户输入的姓名,先声明事件叫sendName

<script setup>
// 方式1:简单声明事件列表
const emit = defineEmits(['sendName'])  
// 方式2:配合TS做类型约束(推荐,尤其大型项目)
const emit = defineEmits<{
  (event: 'sendName', name: string): void
}>()
</script>

这里defineEmits会返回一个emit函数,用来触发事件,如果用TS写法,能明确事件名和传参类型,写错参数类型时IDE会直接报错,特别省心。

步骤2:子组件触发事件并传数据

有了emit函数,子组件里某个操作(比如按钮点击、输入框change)时,就可以触发事件并把数据带出去,比如做个输入框,输入后点按钮传值:

<template>
  <div>
    <input v-model="userName" placeholder="请输入姓名" />
    <button @click="handleSend">传给父组件</button>
  </div>
</template>
<script setup>
import { ref } from 'vue'
const emit = defineEmits(['sendName']) 
const userName = ref('')
const handleSend = () => {
  // 触发sendName事件,把userName的值传出去
  emit('sendName', userName.value)
}
</script>

这里点击按钮时,emit('sendName', ...)就像“喊出”sendName这个信号,同时把userName的值打包发出去。

步骤3:父组件监听事件并处理数据

父组件得“听着”子组件喊的事件,所以要在引用子组件的地方,用@事件名绑定处理函数,比如父组件里这样写:

<template>
  <div>
    <!-- 引用子组件,绑定sendName事件 -->
    <ChildComponent @sendName="getUserName" />
    <p>父组件收到的姓名:{{ receivedName }}</p>
  </div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const receivedName = ref('')
const getUserName = (name) => {
  // name就是子组件传过来的数据
  receivedName.value = name
}
</script>

这样一来,子组件触发sendName时,父组件的getUserName函数就会被调用,参数就是子组件传的数据,完美实现“子→父”的数据传递~

拿实际场景举个例子,更直观!

假设做一个“添加任务”的功能:子组件是输入框+按钮,输入任务内容后,点按钮把任务传给父组件,父组件把任务加到列表里。

子组件(TaskInput.vue)代码:

<template>
  <div class="task-input">
    <input v-model="taskContent" placeholder="请输入任务" />
    <button @click="sendTask">添加任务</button>
  </div>
</template>
<script setup>
import { ref } from 'vue'
const emit = defineEmits(['addTask']) // 声明addTask事件
const taskContent = ref('')
const sendTask = () => {
  if (taskContent.value.trim()) { // 简单做个非空判断
    emit('addTask', taskContent.value) // 触发事件,传任务内容
    taskContent.value = '' // 清空输入框
  }
}
</script>

父组件(TaskList.vue)代码:

<template>
  <div class="task-list">
    <TaskInput @addTask="addNewTask" />
    <ul>
      <li v-for="(task, index) in taskList" :key="index">{{ task }}</li>
    </ul>
  </div>
</template>
<script setup>
import { ref } from 'vue'
import TaskInput from './TaskInput.vue'
const taskList = ref([])
const addNewTask = (task) => {
  taskList.value.push(task) // 把子组件传的任务加到列表
}
</script>

运行起来后,在子组件输入“学习Vue3”,点按钮,父组件的列表里就会新增这一项——这就是子传父在实际功能里的应用,是不是很丝滑?

传值时容易踩的“小坑”,提前避坑!

哪怕步骤看似简单,新手也容易栽跟头,这几个常见问题得注意:

坑1:事件名拼写错,父组件监听不到

子组件里defineEmits(['sendName']),触发时写成emit('sendname')(小写n),父组件@sendName就永远监听不到。事件名要严格一致,建议用驼峰或短横线命名(比如send-name),但触发和监听时要保持大小写、连接符一致。

坑2:忘记用defineEmits声明事件

如果子组件直接写emit('xxx')但没在defineEmits里声明,Vue3开发模式下会报错(生产模式不影响,但开发时不规范),所以一定要先声明事件,再触发!

坑3:传复杂数据(对象/数组)时的响应式问题

如果子组件传的是对象/数组,父组件接收后想修改,得注意Vue的响应式规则,比如子组件传{ name: '张三' },父组件直接给这个对象加新属性(如obj.age = 18),可能不触发更新,这时候要么用reactive包装,要么用Object.assign/扩展运算符重新赋值,保证响应式生效。

Vue3和Vue2子传父有啥区别?

很多从Vue2转Vue3的同学会疑惑,这里简单对比下:

对比项 Vue2(选项式API) Vue3(组合式API + setup)
声明事件 不用显式声明,直接this.$emit 必须用defineEmits声明事件列表或TS类型
触发事件 this.$emit('事件名', 数据) emit('事件名', 数据)(defineEmits返回的emit函数)
类型检查友好度 弱(主要靠文档/约定) 强(配合TS能约束事件名和参数类型)

Vue3的写法更“收敛”,把事件声明和触发都规范化了,尤其是TS支持下,代码健壮性更高,比如Vue2里打错事件名,浏览器控制台可能没报错,但功能失效;Vue3里如果用TS声明事件类型,直接在IDE里就会标红提醒,开发体验好太多~

现在再回头看,Vue3子传父其实就是“子组件喊事件、父组件听事件”的过程,核心靠defineEmits和emit函数联动,只要把声明、触发、监听这三步走顺,再避开标点符号、拼写这些小坑,不管是传简单字符串,还是复杂对象数组,都能稳稳把数据从子传到父~下次写组件通信时,就不会再卡壳啦~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门