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

如何在React中递归渲染Post组件 解决缺少终止条件导致的浏览器崩溃问题

问题原因与修复方案

核心问题定位

  • 递归终止条件错误:JS中数组为引用类型,props.responses == []永远返回false,哪怕无回复也不会触发终止逻辑,直接导致无限递归渲染,浏览器内存占满崩溃
  • Post组件useEffect依赖项为空,外部传入的props更新时无法同步内部状态,导致渲染内容异常
  • 递归列表用index作为key,嵌套层级过多时会触发DOM更新异常

修复后的代码示例

1. 调整递归逻辑与终止条件

// 模拟接口返回的回复数据,每个回复自带children回复列表(后端返回的时候嵌套结构就行)
const sampleResponses = [
  {
    id: 1,
    heading: 'Response Heading 1',
    body: 'Response Body 1',
    footer: 'Response Footer 1',
    children: [] // 无二级回复
  },
  {
    id: 2,
    heading: 'Response Heading 2',
    body: 'Response Body 2',
    footer: 'Response Footer 2',
    children: [
      {
        id: 21,
        heading: '二级回复 Heading',
        body: '二级回复 Body',
        footer: '二级回复 Footer',
        children: []
      }
    ]
  },
  {
    id: 3,
    heading: 'Response Heading 3',
    body: 'Response Body 3',
    footer: 'Response Footer 3',
    children: []
  },
];

// 回复列表组件,终止条件正确
const ResponseList = (props) => {
  // 正确的空判断:没有回复直接返回null
  if (!props.responses?.length) return null;
  return (
    <ul style={{paddingLeft: '20px'}}>
      {
        props.responses.map((response) => {
          // 用每个Post的唯一id作为key,不要用index
          return (
            <li key={response.id}>
              <Post 
                id={response.id}
                heading={response.heading}
                body={response.body}
                footer={response.footer}
                childrenResponses={response.children}
              />
            </li>
          );
        })
      }
    </ul>
  );
}

const Post = (props) => {
  // 不需要把props里的静态内容复制到state,直接用props即可
  const [showResponses, setShowResponses] = useState(false);
  // 子回复列表如果是后端一次性返回就直接用props传,要是按需加载就存在state里调用接口赋值
  const [responses, setResponses] = useState(props.childrenResponses || []);

  const toggleResponses = () => {
    if (!showResponses && !responses.length) {
      // 首次展开调用接口拉取当前Post的回复,这里用模拟数据代替
      setResponses(sampleResponses.filter(item => item.parentId === props.id));
    }
    setShowResponses(!showResponses);
  }

  return (
    <div style={{border: '1px solid #eee', margin: '10px 0', padding: '10px'}}>
      <article>
        <header>
          <h3>{props.heading ?? "默认标题"}</h3>
        </header>
        <main style={{margin: '10px 0'}}>
          {props.body ?? "默认内容"}
        </main>
        <footer style={{fontSize: '12px', color: '#999'}}>
          {props.footer ?? "默认页脚"}
        </footer>
      </article>
      <button onClick={toggleResponses}>
        {showResponses ? '收起回复' : '展开回复'}
      </button>
      {/* 只有展开的时候才渲染回复列表 */}
      {showResponses && <ResponseList responses={responses} />}
    </div>
  );
}

2. 适配你的正式Post组件

//Post.js
import ResponseList from './ResponseList'

export default function Post(props) {
  const [showResponses, setShowResponses] = useState(false);
  const [responses, setResponses] = useState([]);

  const toggleResponses = async () => {
    setShowResponses(!showResponses);
    // 首次展开拉取回复
    if (!showResponses && responses.length === 0) {
      // 调用你的Symfony接口拉取当前postId的回复列表
      const res = await fetch(`/api/posts/${props.id}/responses`);
      const data = await res.json();
      setResponses(data);
    }
  }

  return (
      <Article whiteShade="0.3">
          <PostHeader
              posted={props.posted}
              updated={props.updated}
              authorName={props.authorName}
              responseCount={props.responseCount}
              avatar={props.avatar}
              isEditor={props.isEditor}
              toggleResponses={toggleResponses}
              showResponses={showResponses}
          />
          <PostBody body={props.body} />
          <PostFooter 
              posted={props.posted}
              updated={props.updated}
              authorName={props.authorName}
              avatar={props.avatar}
              responseCount={props.responseCount}
              isEditor={props.isEditor}
          />
          {/* 渲染子回复 */}
          {showResponses && <ResponseList responses={responses} />}
      </Article>
  )
}

额外优化建议

  • 后端返回回复数据时直接返回嵌套结构,前端不需要额外处理层级即可直接递归渲染
  • 嵌套层级过深时可以增加折叠按钮,或者超过3层后跳转至单独的回复详情页,避免页面过长
  • 按需加载回复,不要一次性渲染所有层级的回复,减少初始渲染压力

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 09:06:04