You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 03:24:57