Vue3访问JSON数据元素出现'undefined'错误排查
三级嵌套v-for渲染JSON数据时出现
Cannot read properties of undefined (reading 'threadId')错误 错误信息
Cannot read properties of undefined (reading 'threadId') TypeError: Cannot read properties of undefined (reading 'threadId') at eval (webpack-internal:///./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/PageHome.vue?vue&type=template&id=c0c75584:57:318) at renderList (webpack-internal:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js:2594:16) at eval (webpack-internal:///./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/PageHome.vue?vue&type=template&id=c0c75584:49:450) at renderList (webpack-internal:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js:2594:16) at Proxy.render (webpack-internal:///./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/PageHome.vue?vue&type=template&id=c0c75584:45:343) at renderComponentRoot (webpack-internal:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js:887:38) at ReactiveEffect.componentUpdateFn [as fn] (webpack-internal:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js:5064:46) at ReactiveEffect.run (webpack-internal:///./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js:216:19) at instance.update (webpack-internal:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js:5161:51) at setupRenderEffect (webpack-internal:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js:5169:5)
相关数据结构
{ "categories": [ { "forums": [ "-KpOx5Y4AqRr3sB4Ybwj", "-KsjO4_W3W9Q2Z2UmuPr" ], "name": "Feedback & Information", "slug": "feedback-and-information", "id": "-KpR7vRkiRPpbUd_TVAR" }, ... ], "forums": [ { "categoryId": "-KpR7vRkiRPpbUd_TVAR", "description": "Important announcements about the forum.", "lastPostId": "-KxxU6bYWl-6Xnu9g-56", "name": "Announcements", "slug": "announcements", "threads": [ "-KvgjI6bfbWYlbYV5ndr" ], "id": "-KpOx5Y4AqRr3sB4Ybwj" }, ... ], "threads": [ { "contributors": [ "FsCDAk9w8NeXEceLV87arpsXjnQ2", "L664y3qZSubDbT1R6npC0EEybJ73", "VXjpr2WHa8Ux4Bnggym8QFLdv5C3" ], "firstPostId": "-KsjWehQ--apjDBwSBCZ", "forumId": "-KsjPat5MWCx-dkjNVg8", "lastPostAt": 1594040497, "lastPostId": "-KsjnAAjzfpznUVh4DSa", "posts": [ "-KsjWehQ--apjDBwSBCZ", "-KsjefUiIuNkJieBc5Rq", "-KsjfvciJhJnebrL4Iw7", "-KsjnAAjzfpznUVh4DSa" ], "publishedAt": 1594035908, "slug": "what-kind-of-pet-do-you-have", "title": "What kind of pet do you have?", "userId": "ALXhxjwgY9PinwNGHpfai6OWyDu2", "id": "-KsjWehQ--apjDBwSBCY" }, ... ], "posts": [ { "edited": { "at": 1594037546, "by": "ALXhxjwgY9PinwNGHpfai6OWyDu2", "moderated": false }, "publishedAt": 1594035908, "text": "I'm curious to know what kind...", "threadId": "-KsjWehQ--apjDBwSBCY", "userId": "ALXhxjwgY9PinwNGHpfai6OWyDu2", "id": "-KsjWehQ--apjDBwSBCZ" }, ... ], "users": [ { "avatar": "https://...", "email": "email@gmail.com", "lastVisitAt": 1594772078, "name": "Chris Fritz", "isModerator": true, "registeredAt": 1594632260, "username": "chrisvfritz", "usernameLower": "chrisvfritz", "id": "38St7Q8Zi2N1SPa5ahzssq9kbyp1" }, ... ] }
对应的Vue代码
<template> <div class="col-full"> <div v-for="category in categories" :key="category.id" class="forum-list"> <h2 class="list-title"> <a href="category.html">{{ category.name }}</a> </h2> <div v-for="forumId in category.forums" :key="forumId" class="forum-listing"> <div class="forum-details"> <a class="text-xlarge" href="forum.html">{{ forumById(forumId).name }}</a> <p>{{ forumById(forumId).description }}</p> </div> <div class="threads-count"> <p><span class="count">{{ 7 }}</span> thread</p> </div> <div class="last-thread"> <img class="avatar" :src=postById(forumById(forumId).lastPostId).avatar alt=""> <div class="last-thread-details"> <a href="thread.html">{{ threadById(postById(forumById(forumId).lastPostId).threadId).title }}</a> </div> </div> </div> </div> </template> <script setup> import sourceData from '@/data.json' import { ref } from 'vue' const categories = ref(sourceData.categories) const forums = ref(sourceData.forums) const threads = ref(sourceData.threads) const posts = ref(sourceData.posts) const users = ref(sourceData.users) function forumById(forumId) { return forums.value.find(f => f.id === forumId) } function threadById(threadId) { return threads.value.find(t => t.id === threadId) } function postById(postId) { return posts.value.find(p => p.id === postId) } function userById(userId) { return users.value.find(u => u.id === userId) } </script> <style></style>
问题现象
错误触发在模板第21行:threadById(postById(forumById(forumId).lastPostId).threadId).title,原因是postById(forumById(forumId).lastPostId)可能返回undefined,导致无法读取threadId属性。但用三元表达式包裹后({{ postById(forumById(forumId).lastPostId) ? threadById(postById(forumById(forumId).lastPostId).threadId).title : "oops" }}),内容能正常显示。
解决办法
1. 使用可选链操作符(推荐)
Vue模板支持ES6+的可选链操作符?.,可以简化空值判断,避免报错:
<a href="thread.html">{{ threadById(postById(forumById(forumId).lastPostId)?.threadId)?.title || "oops" }}</a>
同时头像部分也需要处理:
<img class="avatar" :src="postById(forumById(forumId).lastPostId)?.avatar || ''" alt="">
2. 优化工具函数,返回默认空对象
修改工具函数,当找不到对应数据时返回空对象,避免后续属性访问报错:
function forumById(forumId) { return forums.value.find(f => f.id === forumId) || {} } function threadById(threadId) { return threads.value.find(t => t.id === threadId) || {} } function postById(postId) { return posts.value.find(p => p.id === postId) || {} }
这样即使找不到数据,后续访问属性也不会报错,再配合空值合并运算符显示默认内容:
<a href="thread.html">{{ threadById(postById(forumById(forumId).lastPostId).threadId).title || "oops" }}</a>
3. 提前计算嵌套数据(性能更优)
在组件初始化或数据更新时,提前将嵌套数据关联好,避免模板中多次调用查找函数,同时减少空值判断:
import { computed } from 'vue' const enrichedCategories = computed(() => { return categories.value.map(category => ({ ...category, forums: category.forums.map(forumId => { const forum = forumById(forumId) if (!forum) return {} const lastPost = postById(forum.lastPostId) const lastThread = lastPost ? threadById(lastPost.threadId) : {} return { ...forum, lastPost, lastThread } }) })) })
然后模板中直接使用计算好的数据:
<div v-for="category in enrichedCategories" :key="category.id" class="forum-list"> <!-- ... --> <div v-for="forum in category.forums" :key="forum.id" class="forum-listing"> <div class="forum-details"> <a class="text-xlarge" href="forum.html">{{ forum.name }}</a> <p>{{ forum.description }}</p> </div> <!-- ... --> <div class="last-thread"> <img class="avatar" :src="forum.lastPost?.avatar || ''" alt=""> <div class="last-thread-details"> <a href="thread.html">{{ forum.lastThread.title || "oops" }}</a> </div> </div> </div> </div>
这种方式不仅避免了模板中的复杂嵌套调用,还提升了渲染性能,因为计算属性会缓存结果。
内容的提问来源于stack exchange,提问作者robdovDev
相关产品推荐
相关产品推荐

