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

TypeScript React食谱应用:单条数据获取类型报错求助

问题:React+TypeScript useFetch钩子获取单条Recipe数据类型不匹配错误

作为TypeScript新手,开发React食谱应用时,通用useFetch钩子在首页获取所有食谱列表正常,但在Recipe页面获取单条数据时,访问data.title触发类型错误:

Property 'title' does not exist on type 'Recipe[]'.ts(2339)

相关代码

hooks/useFetch.ts

import { useState, useEffect } from 'react'
import { Recipe } from '../../types/types'

type FetchResult = {
  data: Recipe[] | null
  isPending: boolean
  error: string | null
}

export const useFetch = (url: string): FetchResult => {
  const [data, setData] = useState<Recipe[] | null>(null)
  const [isPending, setIsPending] = useState<boolean>(false)
  const [error, setError] = useState<string | null>(null)

  useEffect(() => {
    const abortController = new AbortController()
    const signal = abortController.signal

    setIsPending(true)

    fetch(url, { signal })
      .then((res) => {
        if (!res.ok) {
          throw Error('Could not fetch the data for that resource')
        }
        return res.json()
      })
      .then((data) => {
        setData(data)
        setIsPending(false)
        setError(null)
      })
      .catch((err) => {
        if (err.name === 'AbortError') {
          console.log('Fetch aborted')
        } else {
          setIsPending(false)
          setError(err.message)
        }
      })

    return () => abortController.abort()
  }, [url])

  return { data, isPending, error }
}

types/types.ts

export type Recipe = {
  id: string
  title: string
  ingredients: string[]
  method: string
  cookingTime: string
}

解决方案

核心问题是useFetch钩子固定返回Recipe[] | null类型,但获取单条数据时后端返回的是单个Recipe对象而非数组,导致类型不匹配。以下两种方式可以解决:

方式1:将useFetch改为泛型钩子(推荐)

修改钩子代码,使其支持泛型,能根据请求场景动态指定返回数据类型:

import { useState, useEffect } from 'react'

type FetchResult<T> = {
  data: T | null
  isPending: boolean
  error: string | null
}

export const useFetch = <T,>(url: string): FetchResult<T> => {
  const [data, setData] = useState<T | null>(null)
  const [isPending, setIsPending] = useState<boolean>(false)
  const [error, setError] = useState<string | null>(null)

  useEffect(() => {
    const abortController = new AbortController()
    const signal = abortController.signal

    setIsPending(true)

    fetch(url, { signal })
      .then((res) => {
        if (!res.ok) {
          throw Error('Could not fetch the data for that resource')
        }
        return res.json()
      })
      .then((data) => {
        setData(data)
        setIsPending(false)
        setError(null)
      })
      .catch((err) => {
        if (err.name === 'AbortError') {
          console.log('Fetch aborted')
        } else {
          setIsPending(false)
          setError(err.message)
        }
      })

    return () => abortController.abort()
  }, [url])

  return { data, isPending, error }
}

不同场景的使用方式

  • 首页获取食谱列表:
import { Recipe } from '../types/types'
import { useFetch } from './hooks/useFetch'

const Home = () => {
  const { data: recipes, isPending, error } = useFetch<Recipe[]>('/api/recipes')
  
  if (isPending) return <div>Loading...</div>
  if (error) return <div>{error}</div>
  
  return (
    <div>
      {recipes?.map(recipe => (
        <div key={recipe.id}>{recipe.title}</div>
      ))}
    </div>
  )
}
  • Recipe页面获取单条数据:
import { Recipe } from '../types/types'
import { useFetch } from './hooks/useFetch'

const Recipe = ({ id }: { id: string }) => {
  const { data: recipe, isPending, error } = useFetch<Recipe>(`/api/recipes/${id}`)
  
  if (isPending) return <div>Loading...</div>
  if (error) return <div>{error}</div>
  if (!recipe) return <div>Recipe not found</div>
  
  return <h1>{recipe.title}</h1>
}

方式2:手动类型断言(快速临时解决)

如果不想修改通用钩子,可以在Recipe页面中手动断言数据类型,同时增加非空判断:

import { Recipe } from '../types/types'
import { useFetch } from './hooks/useFetch'

const Recipe = ({ id }: { id: string }) => {
  const { data, isPending, error } = useFetch(`/api/recipes/${id}`)
  const recipe = data as Recipe | null
  
  if (isPending) return <div>Loading...</div>
  if (error) return <div>{error}</div>
  if (!recipe) return <div>Recipe not found</div>
  
  return <h1>{recipe.title}</h1>
}

注意:这种方式需要确保后端接口确实返回单个Recipe对象,否则可能出现运行时错误,泛型方式更安全可靠。

内容的提问来源于stack exchange,提问作者xdreva

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 17:53:31