Vue.js 3中无法访问ref的.value属性问题求助
问题分析与解决
问题现象
在自定义组合式函数中使用ref()定义的error变量,内部可正常访问.value属性,但在组件setup()中导入该组合式函数并调用异步的fetchDoc后,立即访问error.value得到null,而控制台展开RefImpl对象时却能看到内部value为404。
组合式函数代码:
import { ref, reactive } from 'vue' import { doc, getDoc } from 'firebase/firestore' import { db } from '../configs/firebase' export default function () { const docCol = reactive({}) const error = ref(null) const fetchDoc = async (collectionName, collectionID) => { try { const tourCol = doc(db, collectionName, collectionID) const docSnap = await getDoc(tourCol) if (docSnap.exists()) { console.log('==> Document data exists!') Object.assign(docCol, { id: docSnap.id, ...docSnap.data() }) } else { error.value = 404 console.log('No such document!') } } catch (err) { error.value = err console.log(err) } } return { docCol, error, fetchDoc } }
组件setup()代码:
setup() { const route = useRoute() const router = useRouter() const tour = ref({}) const { docCol, error, fetchDoc } = useDoc() fetchDoc('tours', route.params.id) tour.value = docCol console.log('🚀 ~ error:', error) console.log('🚀 ~ error.value:', error.value) if (error.value === 404) { router.push({ name: 'NotFound' }) } return { tour, error } }
用户尝试的无效写法:
const err = ref(error) console.log(err.value)
核心原因
这是异步操作的时序问题加上浏览器控制台的惰性求值特性导致的:
fetchDoc是异步函数,调用后会立即返回Promise,后续的console.log和路由跳转是同步执行的,此时fetchDoc内部的数据库请求还未完成,error.value仍保持初始值null。- 浏览器控制台显示的
RefImpl对象是惰性求值的,当你展开对象时才会读取最新的value,所以会看到内部是404,但同步打印时的error.value确实是null。
解决方法
方案1:使用watch监听error变化
利用Vue的响应式监听,在error.value变化时执行路由跳转:
setup() { const route = useRoute() const router = useRouter() const tour = ref({}) const { docCol, error, fetchDoc } = useDoc() fetchDoc('tours', route.params.id) tour.value = docCol // 监听error的变化 watch(error, (newError) => { if (newError === 404) { router.push({ name: 'NotFound' }) } }) return { tour, error } }
方案2:await异步操作完成
将setup改为异步函数,等待fetchDoc执行完毕后再访问error.value:
async setup() { const route = useRoute() const router = useRouter() const tour = ref({}) const { docCol, error, fetchDoc } = useDoc() // 等待fetchDoc的异步操作完成 await fetchDoc('tours', route.params.id) tour.value = docCol console.log('🚀 ~ error.value:', error.value) if (error.value === 404) { router.push({ name: 'NotFound' }) } return { tour, error } }
注意:使用异步setup时,需要在父组件中用<Suspense>包裹该组件,否则会抛出组件渲染错误。
方案3:新增加载状态(推荐)
在组合式函数中添加加载状态,结合watch监听加载完成和错误状态,逻辑更清晰:
修改组合式函数:
import { ref, reactive } from 'vue' import { doc, getDoc } from 'firebase/firestore' import { db } from '../configs/firebase' export default function () { const docCol = reactive({}) const error = ref(null) const isLoading = ref(true) // 新增加载状态标记 const fetchDoc = async (collectionName, collectionID) => { isLoading.value = true error.value = null // 每次请求前重置错误 try { const tourCol = doc(db, collectionName, collectionID) const docSnap = await getDoc(tourCol) if (docSnap.exists()) { console.log('==> Document data exists!') Object.assign(docCol, { id: docSnap.id, ...docSnap.data() }) } else { error.value = 404 console.log('No such document!') } } catch (err) { error.value = err console.log(err) } finally { isLoading.value = false // 请求结束后标记加载完成 } } return { docCol, error, fetchDoc, isLoading } }
组件中使用:
setup() { const route = useRoute() const router = useRouter() const tour = ref({}) const { docCol, error, fetchDoc, isLoading } = useDoc() fetchDoc('tours', route.params.id) tour.value = docCol // 监听加载完成和错误状态 watch([isLoading, error], () => { if (!isLoading.value && error.value === 404) { router.push({ name: 'NotFound' }) } }) return { tour, error, isLoading } }
内容的提问来源于stack exchange,提问作者Miwody
相关产品推荐
相关产品推荐

