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

