Vue+TS中FetchAPI使用问题排查及响应类型转换咨询
问题概述
我正在开发一款简易笔记应用,需要从服务器获取数据并渲染。已有现成组件和可用后端,但数据获取环节遇到困难,构建时出现多个TypeScript错误。同时想咨询:当响应存在额外字段时,TypeScript能否正确转换类型。
App.vue 代码
<script setup lang="ts"> import Form from "./components/Form.vue"; import NoteItem from "./components/NoteItem.vue"; type Note = { title: string; content: string; } let loaded = false; let noNotes = false; const telegramId = "test" const notes: Promise<Note[]> = fetch(`http://localhost:3000/notes/${telegramId}`) .then((res) => { if (res.status === 404) { noNotes = true; } return res.json() as Note[]; }) .then(() => loaded = true) .catch((err) => console.log(err)); </script> <template> <div class="flex flex-col justify-center items-center justify-items-center h-screen space-x-4 gap-0"> <h1 class="font-bold h-max text-6xl font-serif underline decoration-sky-500">Notes</h1> <Form/> <div class="grid grid-cols-3 w-1/3 mt-8 gap-4"> <div v-if="loaded"> <div v-for="note in notes"> <NoteItem :title="note.title" :content="note.content"/> </div> </div> <div v-else> <h2 v-if="noNotes"></h2> <h2 v-else>Fetching...</h2> </div> </div> </div> </template> <style scoped> </style>
响应示例
[ { "ID": 1, "CreatedAt": "2024-10-16T18:17:25.141109Z", "UpdatedAt": "2024-10-16T18:17:25.141109Z", "DeletedAt": null, "TelegramId": "test", "Title": "Test3", "Content": "Test3" } ]
错误信息
src/App.vue:13:7 - error TS2322: Type 'Promise<boolean | void>' is not assignable to type 'Promise<Note[]>'. Type 'boolean | void' is not assignable to type 'Note[]'. Type 'boolean' is not assignable to type 'Note[]'. 13 const notes: Promise<Note[]> = fetch(`http://localhost:3000/notes/${telegramId}`) ~~~~~ src/App.vue:19:14 - error TS2352: Conversion of type 'Promise<any>' to type 'Note[]' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type 'Promise<any>' is missing the following properties from type 'Note[]': length, pop, push, concat, and 28 more. 19 return res.json() as Note[]; ~~~~~~~~~~~~~~~~~~~~ src/App.vue:32:34 - error TS2339: Property 'title' does not exist on type 'string | (<TResult1 = Note[], TResult2 = never>(onfulfilled?: ((value: Note[]) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>) | (<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<...>) | .....'. Property 'title' does not exist on type 'string'. 32 <NoteItem :title="note.title" :content="note.content"/> ~~~~~ src/App.vue:32:56 - error TS2339: Property 'content' does not exist on type 'string | (<TResult1 = Note[], TResult2 = never>(onfulfilled?: ((value: Note[]) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>) | (<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<...>) | .....'. Property 'content' does not exist on type 'string'. 32 <NoteItem :title="note.title" :content="note.content"/>
解决方案
1. 修正Promise类型与数据存储问题
你将notes定义为Promise<Note[]>,但后续then(() => loaded = true)返回布尔值,导致整个Promise类型变为Promise<boolean | void>,与定义类型不匹配。同时模板无法直接遍历Promise,应使用Vue响应式变量存储数组:
<script setup lang="ts"> import Form from "./components/Form.vue"; import NoteItem from "./components/NoteItem.vue"; import { ref } from 'vue'; type Note = { title: string; content: string; } // 用响应式变量存储笔记数组 const notes = ref<Note[]>([]); let loaded = false; let noNotes = false; const telegramId = "test"; // 单独处理请求逻辑 fetch(`http://localhost:3000/notes/${telegramId}`) .then(async (res) => { if (res.status === 404) { noNotes = true; return; } // 先将响应转为unknown,再做类型断言 const rawData = await res.json() as unknown; // 映射后端字段(注意后端返回的是Title/Content,与定义的字段大小写不一致) notes.value = (rawData as Array<{Title: string; Content: string}>).map(item => ({ title: item.Title, content: item.Content })); }) .then(() => loaded = true) .catch((err) => console.log(err)); </script>
2. 修正类型断言错误
res.json()返回Promise<any>,直接断言为Note[]会触发TypeScript警告,正确做法是先转为unknown再进行类型断言。另外注意后端返回字段为大写开头的Title和Content,与你定义的Note类型中小写开头的字段不匹配,必须手动映射,否则运行时无法取到对应值。
3. 模板遍历修正
现在notes是响应式数组,模板中可直接遍历:
<div v-if="loaded"> <div v-for="note in notes" :key="note.title"> <NoteItem :title="note.title" :content="note.content"/> </div> </div>
关于响应额外字段的TypeScript类型转换问题
TypeScript采用结构类型系统,只要对象包含你定义类型的所有必填字段,即使存在额外字段,编译时也会将其视为该类型的实例,不会报错。但需注意:
- 类型断言仅为编译时检查,运行时不会自动过滤或转换字段,若后端字段名与类型定义不匹配(如大小写差异),必须手动映射。
- 若需严格校验响应数据,可编写类型守卫函数确保数据符合预期,避免运行时错误:
function isNoteArray(data: unknown): data is Note[] { if (!Array.isArray(data)) return false; return data.every(item => typeof item === 'object' && item !== null && 'Title' in item && typeof item.Title === 'string' && 'Content' in item && typeof item.Content === 'string' ); }
随后在请求逻辑中使用:
const rawData = await res.json(); if (isNoteArray(rawData)) { notes.value = rawData.map(item => ({title: item.Title, content: item.Content})); } else { // 处理数据格式错误 console.error('Invalid response format'); }
内容的提问来源于stack exchange,提问作者Sanyokk
相关产品推荐
相关产品推荐

