Nuxt3 Composition API中如何正确使用emit与props实现组件传值
问题排查
当前代码共有3个核心错误,直接导致父子组件数据无法同步:
- 违反Vue单向数据流规则:props是父组件向子组件传递的只读数据,子组件不能直接修改props值,你在子组件中直接给
v-model绑定AirportData这个prop,属于非法修改,哪怕你在子组件里看到值变化,也不会同步回父组件,开发环境下Vue会直接抛出警告。 - 父组件事件回调存在变量名冲突:
handleUpdate函数的形参名和外层定义的ref变量pickuppoint完全同名,函数执行时会优先读取内部形参,你写的pickuppoint.value = pickuppoint本质是给形参对象加value属性,根本修改不到外层的ref变量。 - 子组件取值逻辑冗余:
@item-select事件本身会把选中的机场选项对象作为参数返回,不需要绕一圈读取props里的值,直接拿事件参数传递更准确,也避免props不同步的问题。
正确实现方案
修正后的子组件 components/AirportSearch.vue
子组件内部单独维护选中状态,不直接修改props,选中选项后直接把值抛给父组件,同时支持父组件传入初始值做历史数据回显:
<template> <AutoComplete class="ttc-w-full ttc-h-12" v-model="selectedAirport" :suggestions="airports" @complete="getairports($event)" field="name" placeholder="Search Pickup Location" forceSelection @item-select="handleSelect" > <template #item="slotProps"> <div style="display: flex; justify-content: space-between" class="ttc-font-bold ttc-text-md" > {{ slotProps.item.name }} <span class="ttc-font-normal ttc-text-xs"> {{ slotProps.item.code }} </span> </div> </template> </AutoComplete> </template> <script setup> import { ref, watch } from "vue"; const { apiUrl } = useRuntimeConfig(); const airports = ref([]); const emit = defineEmits(["update:pickupPoint"]) // 接收父组件传入的默认回显值 const props = defineProps({ defaultAirport: { type: Object, default: null } }); // 子组件内部维护选中状态,不直接修改props const selectedAirport = ref(props.defaultAirport); // 监听父组件传入的默认值变化,处理cookie异步加载后的回显 watch(() => props.defaultAirport, (newVal) => { if (newVal) selectedAirport.value = newVal; }) const getairports = useDebounce(async (event) => { const { data } = await useFetch(`${apiUrl}/airports/find`, { method: "post", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ aptsearchkey: event.query, }), }); airports.value = data.value; }, 500); // 选中选项时直接把事件返回的选中值抛给父组件 function handleSelect(event) { emit("update:pickupPoint", event.value); } </script>
修正后的父组件 components/TaxiSearchForm.vue
修复变量名冲突问题,接收子组件传递的值,同时支持选中值自动存cookie:
<template> <div> <AirportSearch :defaultAirport="pickuppoint" @update:pickupPoint="handleUpdate" /> <!-- 调试用,可删除 --> <div v-if="pickuppoint" class="mt-2">已选机场:{{ pickuppoint.name }} ({{ pickuppoint.code }})</div> </div> </template> <script setup> import {ref, watch} from 'vue' const airportTransferData = useCookie("airportTransferData", { maxAge: 60 * 60 * 24 * 7, }); const pickuppoint = ref(null); // 形参改名,避免和外层ref变量重名 function handleUpdate(selectedVal) { pickuppoint.value = selectedVal; }; // 选中值变化时自动同步到cookie watch(pickuppoint, (newVal) => { if (newVal) { airportTransferData.value = { ...airportTransferData.value, pickuppoint: newVal } } }, { deep: true }) onMounted(() => { if (airportTransferData.value?.pickuppoint) { pickuppoint.value = airportTransferData.value.pickuppoint; } }); </script>
优化建议
如果想写得更简洁,可以把子组件的emit名改成update:modelValue,父组件直接用v-model="pickuppoint"绑定,不需要手动写事件监听,是Vue3官方推荐的组件双向绑定写法。
你提供的机场接口返回数据结构是标准对象,不需要额外拆字段,直接传整个对象即可,后续存cookie、提交表单都可以直接用。
内容的提问来源于stack exchange,提问作者imgrv
相关产品推荐
相关产品推荐

