Next.js+TS异步组件返回Promise<Element>非合法JSX报错问询
报错根因
- 组件入参写法错误:React组件的第一个入参固定为props对象,你直接将
id: string设为组件入参,JSX调用组件时默认传入的是空props对象{},因此触发Type '{}' is not assignable to type 'string'类型错误。 - 客户端组件不支持async声明:给组件加async关键字后,返回值会变为
Promise<JSX.Element>,而合法JSX元素必须是同步返回的ReactElement类型,因此触发组件无法作为JSX组件使用的报错。手动通过类型标注强转Promise类型无法解决问题,这是React运行时的硬性校验规则,不是单纯的TS类型问题。 - 附带逻辑bug:原有
getItemData函数中(await itemData).json()的返回值是Promise,缺少await会导致最终拿到的是Pending状态的Promise对象,不是实际接口返回数据。
修复方案
根据你使用Next.js的渲染模式选对应方案即可。
方案1:客户端组件(兼容Pages Router、App Router加了'use client'的组件)
客户端组件不能声明为async,数据请求需要放到副作用函数中,同时修正props入参写法,补全类型定义:
// App Router客户端组件需要加下面这行,Pages Router删除即可 'use client'; import { useEffect, useState } from 'react'; // 替换成你项目内的组件实际引入路径 import Button from '@/components/Button'; import ItemContent from '@/components/ItemContent'; // 定义组件props类型 type ItemProps = { id: string; } const Item = ({ id }: ItemProps) => { // 初始化状态,可加默认加载态 const [itemData, setItemData] = useState<{name: string; description: string}>({ name: '加载中', description: '正在获取商品信息...' }); useEffect(() => { const fetchData = async () => { try { const data = await getItemData(id); setItemData(data); } catch (err) { setItemData({ name: 'Error', description: "Couldn't fetch the latest items :( ..." }) } }; fetchData(); }, [id]); return ( <Button className="item"> <ItemContent name={itemData.name} description={itemData.description} /> </Button> ); };
同步修正getItemData函数的逻辑,补全await和类型定义:
async function getItemData(item: string): Promise<{name: string; description: string}> { if (item === "") { return { name: "Error", description: "Couldn't fetch the latest items :( ..." } } try { const res = await fetch("/api/items/getItem/", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ id: item }) }); if (!res.ok) throw new Error('Request failed'); // 补全await,等待json解析完成 return await res.json(); } catch (err) { console.error(err); throw new Error("Failed to get item data"); } }
方案2:Next.js App Router 服务端组件
如果你使用App Router的服务端组件,可以直接用async写法,但必须遵守组件入参规则,第一个参数为props对象:
// 服务端组件不能加'use client',也不能使用useEffect、useState等客户端hook // 替换成你项目内的组件实际引入路径 import Button from '@/components/Button'; import ItemContent from '@/components/ItemContent'; type ItemProps = { id: string; } // 服务端组件支持async声明 const Item = async ({ id }: ItemProps) => { const itemData = await getItemData(id); return ( <Button className="item"> <ItemContent name={itemData.name} description={itemData.description} /> </Button> ); };
注意:服务端组件只能在服务端组件树中直接使用,若要传给客户端组件作为children,需要先在服务端渲染完成后再传入。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

