Vue3异步Composable导出值无响应,如何实现状态更新通知?
问题分析与解决方案
核心问题根源
- 异步函数返回Promise导致解构失败:
useNavigation被定义为async函数,它的返回值是一个Promise对象,而非直接的响应式对象。组件中直接解构const { main, test } = useNavigation(),实际上是从Promise上读取不存在的属性,所以初始打印结果为undefined。 - forEach无法处理异步等待:
Object.keys(navs).forEach(async (key) => { ... })中,forEach不会等待内部的异步请求完成,虽然最终navs会被正确填充,但组件因第一步的错误根本没有拿到有效的响应式引用,导致watch无法触发。
修正后的代码实现
composables/useNavigation.js
// 全局共享状态变量 const navs = reactive({ main: false, footer: false, }) // 标记是否已加载,避免重复请求 let isLoaded = false export const useNavigation = () => { const runTimeConfig = useRuntimeConfig() if (!isLoaded) { isLoaded = true const fetchMenus = async () => { const endpointMenus = `${runTimeConfig.public.API_URL}/wp-api-menus/v2/menus` const { data: menus } = await useFetch(endpointMenus) // 用Promise.all并行处理多个菜单请求,提升效率 const menuRequests = Object.keys(navs).map(async (key) => { const menuID = menus.value?.find(m => m.slug === key)?.ID if (!menuID) return const endpointMenu = `${runTimeConfig.public.API_URL}/wp-api-menus/v2/menus/${menuID}` const { data: menu } = await useFetch(endpointMenu) navs[key] = menu.value }) await Promise.all(menuRequests) } fetchMenus() } return { ...toRefs(navs), test: toRef(navs.main), } }
/pages/[slug].vue
<script setup> const { main, test } = useNavigation() // 注意:在<script setup>中访问ref值需要用.value console.log('main初始值: ', main.value) console.log('test初始值: ', test.value) watch( main, (newValue, oldValue) => { console.log('main更新后的值: ', newValue) }, { deep: true, immediate: true }, ) const menu = computed(() => { console.log('computed中的main: ', main.value) return main.value }) setTimeout(() => { console.log('3秒后main的值: ', main.value) console.log('3秒后test的值: ', test.value) }, 3000) </script>
关键修改说明
- 移除async标记:将异步逻辑封装在内部函数
fetchMenus中调用,让useNavigation直接返回响应式引用,避免组件拿到Promise对象。 - 避免重复请求:添加
isLoaded标记,确保菜单数据只会被请求一次,即使多个组件调用该组合式函数。 - 并行处理异步请求:用
map+Promise.all替代forEach,既保证所有异步请求完成后更新状态,又提升请求效率。 - 正确访问ref值:在
<script setup>的JavaScript代码中,需要通过.value访问ref的实际值(模板中可省略)。
内容的提问来源于stack exchange,提问作者Merc
相关产品推荐
相关产品推荐

