React中Contentful富文本渲染异步获取嵌入资产报错解决
解决React中使用Contentful documentToReactComponent异步加载图片资产的问题
你遇到的错误核心是:documentToReactComponent的渲染节点函数需要同步返回React元素,但你直接在里面执行了异步请求并返回Promise,而Promise不是有效的React子元素。下面提供两种可行的解决方案:
方案一:封装异步图片组件(推荐)
把异步获取图片资产的逻辑封装到独立组件中,利用React的状态管理处理加载、成功、失败的状态:
import { useState, useEffect } from 'react'; // 自定义异步图片组件 const ContentfulImage = ({ assetId, width, height }) => { const [imageData, setImageData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchAsset = async () => { try { const asset = await client.getAsset(assetId); setImageData({ url: asset.fields.file.url, alt: asset.fields.title || asset.fields.file.fileName }); } catch (err) { setError(err); console.error('加载图片资产失败:', err); } finally { setLoading(false); } }; fetchAsset(); }, [assetId]); if (loading) return <div className="image-loading">加载中...</div>; if (error) return <div className="image-error">图片加载失败</div>; if (!imageData) return null; return ( <Image width={width} height={height} src={`https://${imageData.url}`} alt={imageData.alt} /> ); }; // 修改富文本渲染配置 const RICHTEXT_OPTIONS = { renderNode: { [BLOCKS.PARAGRAPH]: (node, children) => <p>{children}</p>, [INLINES.HYPERLINK]: (node, children) => <a href={node?.data.uri}>{children}</a>, [BLOCKS.EMBEDDED_ASSET]: (node) => { const id = node?.data.target.sys.id; return <ContentfulImage assetId={id} width={400} height={400} />; }, }, }; // 组件保持不变 function ClickedArticle({ article }) { const { title, date, author, Img, content } = article; return ( <div className={styles.clickedArticle__article__content}> {documentToReactComponents(content, RICHTEXT_OPTIONS)} </div> ) }
这种方案的优势是逻辑解耦,每个图片独立处理自身的加载状态,无需提前收集资产ID,实现简单。
方案二:预加载所有图片资产
如果文章中的图片数量较多,可以提前批量获取所有资产信息,减少请求次数:
import { useState, useEffect } from 'react'; function ClickedArticle({ article }) { const { title, date, author, Img, content } = article; const [assetsMap, setAssetsMap] = useState({}); // 收集内容中所有嵌入的资产ID const collectAssetIds = (contentDoc) => { const ids = []; const traverse = (node) => { if (node.nodeType === BLOCKS.EMBEDDED_ASSET) { ids.push(node.data.target.sys.id); } if (node.content) { node.content.forEach(traverse); } }; contentDoc.content.forEach(traverse); return ids; }; useEffect(() => { const assetIds = collectAssetIds(content); if (assetIds.length === 0) return; // 批量获取所有资产 const fetchAssets = async () => { const assets = await Promise.all(assetIds.map(id => client.getAsset(id))); const assetMap = assets.reduce((acc, asset) => { acc[asset.sys.id] = { url: asset.fields.file.url, alt: asset.fields.title || asset.fields.file.fileName }; return acc; }, {}); setAssetsMap(assetMap); }; fetchAssets(); }, [content]); // 动态生成富文本渲染配置 const RICHTEXT_OPTIONS = { renderNode: { [BLOCKS.PARAGRAPH]: (node, children) => <p>{children}</p>, [INLINES.HYPERLINK]: (node, children) => <a href={node?.data.uri}>{children}</a>, [BLOCKS.EMBEDDED_ASSET]: (node) => { const id = node?.data.target.sys.id; const imageData = assetsMap[id]; if (!imageData) return <div>加载中...</div>; return ( <Image width={400} height={400} src={`https://${imageData.url}`} alt={imageData.alt} /> ); }, }, }; return ( <div className={styles.clickedArticle__article__content}> {documentToReactComponents(content, RICHTEXT_OPTIONS)} </div> ) }
这种方案通过批量请求提升性能,适合图片密集的内容场景,避免多次单个请求。
内容的提问来源于stack exchange,提问作者Redhewlett
相关产品推荐
相关产品推荐

