Vue3组合式函数mounted钩子中无法获取数据的问题
核心原因拆解
异步请求的时序问题
useFetch中的watchEffect会在组件挂载时触发,但内部的fetch是异步操作。onMounted钩子执行时,网络请求还处于pending状态,此时data.value还是初始的null,所以JSON.stringify(data)会输出null。Ref对象的打印特性
直接console.log(data)时,浏览器会显示Ref对象的实时引用状态——初始显示null,当请求完成data.value更新后,浏览器会自动刷新这个对象的显示,所以你会看到“先显示null后填充数据”的现象;但JSON.stringify(data)是序列化执行瞬间的Ref对象,此时value属性为null,所以输出固定为null。Ref值的访问错误
直接访问data.object是错误的:data是Vue的Ref包装对象,不是原始数据,必须通过.value才能获取内部的实际值。当data.value还是null时,访问null.object就会触发类似“Cannot read properties of null”的错误(你看到的“data is undefined”应该是表述偏差,本质是访问了null的属性)。
解决方案
1. 监听数据变化再执行逻辑
如果需要在数据就绪后处理逻辑,不要在onMounted里硬取,而是用watch监听data的变化:
<script setup> import { ref, computed, watch } from 'vue' import { useFetch } from '../composables/http.js' const baseUrl = 'https://jsonplaceholder.typicode.com/todos/' const id = ref('1') const url = computed(() => baseUrl + id.value) const { data, error, retry } = useFetch(url) // 监听data,数据更新时触发 watch(data, (newData) => { if (newData) { console.log(JSON.stringify(newData)) console.log(newData) // 安全访问属性 console.log(newData.title) } }, { immediate: true }) // immediate: true 会在监听初始化时执行一次 </script>
2. 正确访问Ref值
任何时候操作Ref的实际数据,都必须通过.value属性:
onMounted(() => { // 打印当前的data值 console.log(JSON.stringify(data.value)) // 安全访问属性 if (data.value) { console.log(data.value.title) } })
3. 让useFetch返回就绪Promise(可选)
如果需要在特定时机(比如onMounted)等待数据加载完成,可以修改useFetch,添加一个返回Promise的接口:
// composables/http.js import { ref, watchEffect, toValue } from 'vue' export function useFetch(url) { const data = ref(null) const error = ref(null) let resolveReady = null // 创建一个用于等待数据就绪的Promise const ready = new Promise(resolve => { resolveReady = resolve }) watchEffect(async () => { data.value = null error.value = null const urlValue = toValue(url) try { const res = await fetch(urlValue) data.value = await res.json() resolveReady(data.value) } catch (e) { error.value = e resolveReady(null) } }) return { data, error, ready } }
然后在组件中使用:
<script setup> import { ref, computed, onMounted } from 'vue' import { useFetch } from '../composables/http.js' const baseUrl = 'https://jsonplaceholder.typicode.com/todos/' const id = ref('1') const url = computed(() => baseUrl + id.value) const { data, error, ready } = useFetch(url) onMounted(async () => { // 等待数据就绪 const loadedData = await ready if (loadedData) { console.log(JSON.stringify(loadedData)) } }) </script>
补充说明
Vue的mounted阶段仅表示组件DOM已挂载完成,不保证异步请求已经结束。异步操作的完成时机取决于网络速度,和组件生命周期没有绑定关系,所以不要假设mounted时异步数据一定就绪。
内容的提问来源于stack exchange,提问作者Riccardo Mel

