如何在React中调用API获取并展示指定ID的树形结构全量评论
解决方案
你当前使用的搜索接口返回的children字段仅为评论ID集合,不包含完整的评论内容,想要获取指定ID对应的全量树形评论,需要请求单item详情接口https://hn.algolia.com/api/v1/items/[故事ID],该接口返回的children字段才是嵌套的完整评论结构。
完整实现代码
import { useState, useEffect } from 'react'; // 递归渲染树形评论的组件 const CommentTree = ({ comments }) => { return ( <div style={{ marginLeft: '20px', marginTop: '10px' }}> {comments.map(comment => ( <div key={comment.id} className="comment-item"> {/* 跳过已删除的评论 */} {comment.text && ( <> <div className="comment-author" style={{ color: '#666', fontSize: '14px' }}> {comment.author} · {comment.points || 0} 点 </div> <div className="comment-content" style={{ margin: '5px 0', fontSize: '14px' }} dangerouslySetInnerHTML={{ __html: comment.text }} /> {/* 递归渲染子评论 */} {comment.children?.length > 0 && <CommentTree comments={comment.children} />} </> )} </div> ))} </div> ) } function News() { const [stories, setStories] = useState([]); // 存储当前展开评论的故事ID、对应评论数据、加载状态 const [activeStoryId, setActiveStoryId] = useState(null); const [storyComments, setStoryComments] = useState([]); const [loadingComments, setLoadingComments] = useState(false); useEffect(() => { const getStoriesData = async () => { await fetch("https://hn.algolia.com/api/v1/search_by_date?tags=(story,poll)") .then((response) => response.json()) .then((data) => { // 新增存储故事的objectID,用于后续请求评论详情 const stories = data.hits.map(({objectID, title,url,points,children}) => ({ id: objectID, title, url, points, commentCount: children.length // 原children是评论ID列表,仅用来算数量 })) setStories(stories); }); } getStoriesData(); }, []); // 点击评论数触发获取评论详情的逻辑 const loadComments = async (storyId) => { // 点击已展开的评论则收起 if (activeStoryId === storyId) { setActiveStoryId(null); setStoryComments([]); return; } setLoadingComments(true); setActiveStoryId(storyId); try { const res = await fetch(`https://hn.algolia.com/api/v1/items/${storyId}`); const data = await res.json(); setStoryComments(data.children || []); } catch (err) { console.error('评论加载失败', err); setStoryComments([]); } finally { setLoadingComments(false); } } return( <div className="news"> <table className = "table"> <thead> <tr><th>Title</th> <th>Points</th> <th>Comments</th></tr> </thead> <tbody> {stories.map((story) => ( <tr key={story.id}> <td><a className="title" href={story.url}>{story.title}</a></td> <td><div className="points">{story.points}</div></td> <td> <div className="comments" onClick={() => loadComments(story.id)} style={{ cursor: 'pointer', color: '#007bff' }} > {story.commentCount} </div> {/* 当前行是选中的故事,渲染评论区 */} {activeStoryId === story.id && ( <tr> <td colSpan={3}> {loadingComments ? <p>评论加载中...</p> : <CommentTree comments={storyComments} />} </td> </tr> )} </td> </tr> ))} </tbody> </table> </div> ) } export default News;
关键逻辑说明
- 递归评论组件:通过
CommentTree组件递归渲染嵌套的评论结构,自动适配任意层级的评论树,无需手动处理层级逻辑。 - 懒加载评论:仅在用户点击对应故事的评论数时才请求评论详情,避免初始加载时大量并发请求拖慢性能。
- 适配接口规则:返回的评论内容是HTML格式,所以使用
dangerouslySetInnerHTML渲染,已删除评论没有text字段,渲染时做了过滤处理。
内容的提问来源于stack exchange,提问作者YASH KUMAR
相关产品推荐
相关产品推荐

