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
相关产品推荐
相关产品推荐

